_cmd in Swift for selector use - objective-c

I'm trying to write the following ObjC code in Swift 3:
- (void)scrollViewScroll:(UIScrollView*)scrollView {
// some code
if ([_userDelegate respondsToSelector:_cmd]) {
[_userDelegate scrollViewDidEndDecelerating:scrollView];
}
}
But do not know what to replace _cmd with. I'm trying function, but it doesn't work:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// some code
if (userDelegate?.responds(to: #function))! {
userDelegate?.scrollViewDidScroll!(scrollView)
}
}
using #selector(scrollViewDidScroll(_:)) works, but is there a way to keep it generic?
Edit: Possible duplicate answer is about getting function name which isn't what I'm asking above

Swift doesn't have selectors.
Objective-C sends messages to objects while Swift calls functions. So checking if object can respond to selector is part of Objective-C and NSObject.
Swift protocol functions are required by default. Swift compiler doesn't let you skip those function implementations. But you can make them optional, and you have to check, if these functions implemented before calling.
In this case, just call function with question mark at the end, like this
if let returnValue = userDelegate?.theOptionalFunction?(arguments) {
// you got value
} else {
// delegate returned nil or delegate function isn't implemented
}
Source: The Swift Programming Language
An optional protocol requirement can be called with optional chaining, to account for the possibility that the requirement was not implemented by a type that conforms to the protocol. You check for an implementation of an optional method by writing a question mark after the name of the method when it is called, such as someOptionalMethod?(someArgument).

Related

How can you implement the NSDocument method -canCloseDocumentWithDelegate:shouldCloseSelector:contextInfo: in Swift?

In my application, a NSDocument subclass mission-critical hardware – users really don’t want to close a document by accident! So, I’ve implemented canCloseDocumentWithDelegate… to show an NSAlert and ask before closing.
I am now trying to implement this same thing in an application written in Swift.
Since the answer comes asynchronously, the “should close” result is passed to a callback on a delegate, and not simply returned. In the documentation for -canCloseDocumentWithDelegate:shouldCloseSelector:contextInfo:, it says:
The shouldCloseSelector callback method should have the following signature:
- (void)document:(NSDocument *)doc shouldClose:(BOOL)shouldClose contextInfo:(void *)contextInfo
So, as there’s 3 arguments of different types, I cannot use the simple performSelector:withObject: style methods – you have to use NSInvocation. Note that the delegate is of type id, and the signature above does not appear in any formal protocol – you can’t simply call the method normally. (See this mailing list post for example of how this should be done)
Now, the issue is, NSInvocation is not allowed in Swift! See Swift blog “What Happened to NSMethodSignature”:
Bringing the Cocoa frameworks to Swift gave us a unique opportunity to look at our APIs with a fresh perspective. We found classes that we didn't feel fit with the goals of Swift, most often due to the priority we give to safety. For instance, some classes related to dynamic method invocation are not exposed in Swift, namely NSInvocation and NSMethodSignature.
That sounds like a good thing, but falls down when a simple NSDocument API requires NSInvocation still! The real solution to this whole problem would be for Apple to introduce a new canCloseDocument… API using a block callback. But until that happens, what’s the best solution?
You can solve this with some low level runtime functions:
override func canCloseDocumentWithDelegate(delegate: AnyObject, shouldCloseSelector: Selector, contextInfo: UnsafeMutablePointer<Void>) {
let allowed = true // ...or false. Add your logic here.
let Class: AnyClass = object_getClass(delegate)
let method = class_getMethodImplementation(Class, shouldCloseSelector)
typealias signature = #convention(c) (AnyObject, Selector, AnyObject, Bool, UnsafeMutablePointer<Void>) -> Void
let function = unsafeBitCast(method, signature.self)
function(delegate, shouldCloseSelector, self, allowed, contextInfo)
}
If you need to move this behaviour to another method (eg. after a sheet gets confirmation from the user), simply store the delegate and shouldCloseSelector in properties so you can access them later.
So, my current solution to this, is to keep using Objective-C to perform the NSInvocation. The NSDocument subclass is written in Swift, and calls an Objective-C category to do this bit of work.
Since NSInvocation does not exist in Swift, I really don’t see any other way.
- (void)respondToCanClose:(BOOL)shouldClose delegate:(id)delegate selector:(SEL)shouldCloseSelector contextInfo:(void *)contextInfo
{
NSDocument *doc = self;
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[delegate methodSignatureForSelector:shouldCloseSelector]];
invocation.target = delegate;
invocation.selector = shouldCloseSelector;
[invocation setArgument:&doc atIndex:2]; // Note index starts from 2 - 0 & 1 are self & selector
[invocation setArgument:&shouldClose atIndex:3];
[invocation setArgument:&contextInfo atIndex:4];
[invocation invoke];
}
You can see my sample project:
https://github.com/DouglasHeriot/canCloseDocumentWithDelegate
Another option is to use Objective-C to wrap around objc_msgSend, which is also unavailable in Swift. http://www.cocoabuilder.com/archive/cocoa/87293-how-does-canclosedocumentwithdelegate-work.html#87295
At least as of Swift 4.1, you can do something like:
// Application Logic
myDocument.canClose(
withDelegate: self,
shouldClose: #selector(MyClass.document(_:_:_:)),
contextInfo: nil)
...
// Handler
#objc
private func document(_ doc: NSDocument, _ shouldClose: Bool, _ contextInfo: UnsafeMutableRawPointer) {
...
}
Here is a Swift solution to this issue that I received from Apple Developer Technical Support:
override func canCloseDocumentWithDelegate(delegate: AnyObject, shouldCloseSelector: Selector, contextInfo: UnsafeMutablePointer<Void>) {
super.canCloseDocumentWithDelegate(self, shouldCloseSelector: "document:shouldClose:contextInfo:", contextInfo: contextInfo)
}
func document(doc:NSDocument, shouldClose:Bool, contextInfo:UnsafeMutablePointer<Void>) {
if shouldClose {
// <Your clean-up code>
doc.close()
}
}

What is the swift equivalent to setting properties on `id`?

I wonder what's the Swift equivalent in calling a method on id in which the availability of the method is determined at runtime. Specifically I'm looking to do this pattern in Swift:
-(IBAction) handleEvent:(id) sender {
BOOL didDisable = NO;
if([sender respondsToSelector:#selector(setEnabled:)]) {
[sender setEnabled:NO];
didDisable = YES;
}
[self doSomethingAsyncWithCompletionHandler:^{
if(didDisable) {
[sender setEnabled:YES];
}
}];
}
The biggest problem is that setEnabled: is imported in Swift as a property (e.g. UIBarItem) and none of the following constructs compile
func handleEvent(sender: AnyObject) {
// Error: AnyObject does not have a member named "enabled"
sender.enabled? = false
// Error: (BooleanLiteralCompatible) -> _ is not identical to Bool
sender.setEnabled?(false)
}
You can in fact do it exactly the same way you were doing it before: by calling respondsToSelector:. Indeed, that is exactly what your proposed expression does:
sender.setEnabled?(false)
That expression is actually a shorthand - it calls respondsToSelector: first, and then calls setEnabled: only if the respondsToSelector: test passes. Unfortunately, as you say, you can't get that code to compile. That, however, is merely a quirk of Swift's known repertory of available methods. The fact is that, although it is a little tricky to get it to compile, it can be done - and once you get it to compile, it behaves just as you would expect.
However, I'm not going to explain how to make it compile, because I don't want to encourage this kind of trickery. This sort of dynamic messaging is discouraged in Swift. In general, dynamic messaging tricks such as key-value coding, introspection, and so forth are not needed in Swift and are not consonant with Swift's strong typing approach. It would be better to do things the Swift way, by casting optionally to something that you have reason to believe this thing might be and that has an enabled property. For example:
#IBAction func doButton(sender: AnyObject) {
switch sender {
case let c as UIControl: c.enabled = false
case let b as UIBarItem: b.enabled = false
default:break
}
}
Or:
#IBAction func doButton(sender: AnyObject) {
(sender as? UIControl)?.enabled = false
(sender as? UIBarItem)?.enabled = false
}
In Swift 2.0 beta 4, your prayers are answered; this code becomes legal:
#IBAction
func handleEvent(sender: AnyObject) {
if sender.respondsToSelector("setHidden:") {
sender.performSelector("setHidden:", withObject: true)
}
}
If you want to avoid using the respondsToSelector: method you could define a protocol instead. Then extend the classes you want to use that is already in conformance with this protocol's definition (enabled) and define the function with a generic variable conforming to your protocol.
protocol Enablable{
var enabled:Bool { get set }
}
extension UIButton : Enablable {}
extension UIBarButtonItem : Enablable {}
//....
func handleEvent<T:Enablable>(var sender: T) {
sender.enabled = false
}
If you need to use it with an IBAction method a little bit of a work around is required since you cannot use generics directly on them.
#IBAction func handleEventPressed(sender:AnyObject){
handleEvent(sender);
}
We also need a matching generic function without Enablable conformance so that we can call handleEvent without knowing wether or not sender is Enablable. Luckily the compiler is smart enough to figure out which of the two generic functions to use.
func handleEvent<T>(var sender: T) {
//Do Nothing case if T does not conform to Enablable
}
As a workaround/alternative, you can use Key-Value Coding:
#IBAction func handler(sender: AnyObject) {
if sender.respondsToSelector("setEnabled:") {
sender.setValue(false, forKey:"enabled")
}
}
This works with both Swift 1.2 (Xcode 6.4) and Swift 2.0 (Xcode 7 beta).

Delegate written in Swift not matching selector expected by Objective-C

I'm attempting to use an Objective C wrapper for a Flickr API (ObjectiveFlickr). The wrapper works with an Objective-C code example. I'm trying to use it with Swift and as soon as the wrapper tries to find the first delegate method, it fails on this assertion.
NSAssert([delegate respondsToSelector:#selector(flickrAPIRequest:didObtainOAuthRequestToken:secret:)],
#"Delegate must implement the method -flickrAPIRequest:didObtainOAuthRequestToken:secret: to handle OAuth request token callback");
[delegate flickrAPIRequest:self didObtainOAuthRequestToken:oat secret:oats];
Here's the delegate method signature.
class FlickrDelegate : NSObject, OFFlickrAPIRequestDelegate
{
func flickrAPIRequest(inRequest:OFFlickrAPIRequest,
didObtainOAuthRequestToken inToken:String,
secret inSecret:String) {
...
}
}
I'm a complete novice with what I'm trying to do, so perhaps I've made some terribly dumb mistake.
Did you set your delegate property on flickrAPIRequest class in viewdidload to receive messages from Flikr?

Changing objective-c method after wide implementation

I have a method which is used widely throughout my app, it looks like so:
-(void)commandWithParams:(NSMutableDictionary*)params command:(NSString *) command method: (NSString *) method onCompletion:(JSONResponseBlock)completionBlock { }
This is used to carry out REST calls to an API. After using it all over the place i realize that i need to add another parameter (which will be used only some of the time), and i'm wondering what the best way to do this is.
I thought about using a using a parameter which defaults to nil if not specified, but apparently this is a no-go in objective c (?)
How should i go about changing it? Do i have to change it everywhere it's called and pass nil? If so, any neat functions in xCode to do this without too much hassle?
Thanks.
There are no optional arguments in Objective-C. What you could do instead is use two separate methods.
Imagine you had this method:
- (void)methodWithArgument:(NSString *)str {
// block A
}
Now you need to add a new argument, but don't want to specify it everywhere in your code.
Create a new method with the additional argument and move your implementation to it. How you handle the additional argument, depends on what the method does. From your old method, call the new one with nil as the new argument:
- (void)methodWithArgument:(NSString *)str andArgument:(NSString *)str2 {
if (str2 != nil) {
// do something.
}
// block A
}
- (void)methodWithArgument:(NSString *)str {
[self methodWithArgument:str andArgument:nil];
}
This will work as if you had a method with an optional parameter, that defaults to nil (or whatever you chose). It's a common design pattern and you see it all over Apple's frameworks too.
As an alternative to the method above you may do refactoring in AppCode. It allows to do such of things.

Handling Callbacks

I have a method in an objective-C class. It has 2 callback functions written in C. The class pointer i.e. self is passed to these functions as void *. In the C functions I create a pointer of type class and assign the void * parameter.
The first callback function executes successfully. But the void * pointer becomes nil in the 2nd callback function. Note that I haven't tweaked pointer in the first callback but still I get nil in 2nd callback.
Any ideas what might be going wrong?
For example:
kr = IOServiceAddMatchingNotification(gNotifyPort, kIOFirstMatchNotification,
matchingDict, RawDeviceAdded, NULL,
&gRawAddedIter);
RawDeviceAdded(NULL, gRawAddedIter, self);
This works fine. But below function receives self as nil.
kr = IOServiceAddMatchingNotification(gNotifyPort, kIOFirstMatchNotification,
matchingDict, BulkTestDeviceAdded, NULL,
&gBulkTestAddedIter);
BulkTestDeviceAdded(NULL, gBulkTestAddedIter, self);
Are your problems specifically with the IOKit callback routines? The problem with the specific example you gave is that the IOServiceMatchingCallback takes only 2 parameters, not 3. You need your RawDeviceAdded() and BulkTestDeviceAdded() callback functions to match the IOServiceMatchingCallback prototype and to accept self as the first parameter (refCon), not the 3rd. Also, you need to pass in self as the second-to-last parameter of IOServiceAddMatchingNotification() to get it passed back to you by the callback.
A common method for handling C callbacks in Objective-C code is just to have a static function that forwards the callback to your instance. So, your example callback code would look like this:
static RawDeviceAdded(void* refcon, io_iterator_t iterator)
{
[(MyClass*)refcon rawDeviceAdded:iterator];
}
#implementation MyClass
- (void)setupCallbacks
{
// ... all preceding setup snipped
kr = IOServiceAddMatchingNotification(gNotifyPort,kIOFirstMatchNotification, matchingDict,RawDeviceAdded,(void*)self,&gRawAddedIter );
// call the callback method once to 'arm' the iterator
[self rawDeviceAdded:gRawAddedIterator];
}
- (void)rawDeviceAdded:(io_iterator_t)iterator
{
// take care of the iterator here, making sure to complete iteration to re-arm it
}
#end
Generally, callbacks in Objective-C are handled by passing a delegate object and a selector to perform on that delegate. For example, this method will call a method on its delegate after logging a message, passing both itself and the message that was logged.
- (void)logMessage:(NSString *)message
delegate:(id)delegate
didLogSelector:(SEL)didLogSelector
{
NSLog(#"%#", message);
if (delegate && didLogSelector && [delegate respondsToSelector:didLogSelector]) {
(void) [delegate performSelector:didLogSelector
withObject:self
withObject:message];
}
}
You might call it in code like this:
- (void)sayHello
{
[logger logMessage:#"Hello, world"
delegate:self
didLogSelector:#selector(messageLogger:didLogMessage:)];
}
- (void)messageLogger:(id)logger
didLogMessage:(NSString *)message
{
NSLog(#"Message logger %# logged message '%#'", logger, message);
}
You can also use objc_msgSend() directly instead, though you need to understand the Objective-C runtime enough to choose which variant to use and how to construct the prototype and function pointer through which to call it. (It's the mechanism by which message sends are actually implemented in Objective-C — what the compiler normally generates calls to in order to represent [] expressions.)
This is what Objective-C's selector is for:
http://developer.apple.com/iphone/library/documentation/Cocoa/Reference/NSInvocationOperation_Class
The API isn't very intuitive, but its fine once you understand it
You might need to do some refactoring as well, now there might be a better way, but when I had this problem my solution was to refactor and use InvoationOperation.