Struggling to Convert Objective C Selector and Target Signature to Swift

Struggling to convert Objective C selector and target signature to Swift

To convert from an Objective-C method name to Swift the first parameter name in the Objective C method becomes the function name and then the remaining parameters become the parameters of the function.

In your case, the first parameter name is image, so the function name in Swift will be image.

So,

- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo

becomes the slightly odd looking -

func image(image: UIImage, didFinishSavingWithError: NSError, contextInfo:UnsafePointer<Void>)       {

}

To make things a bit simpler, you an use a different internal parameter name for the error -

func image(image: UIImage, didFinishSavingWithError error: NSError, contextInfo:UnsafePointer<Void>)       {

}

@selector() in Swift?

Swift itself doesn't use selectors — several design patterns that in Objective-C make use of selectors work differently in Swift. (For example, use optional chaining on protocol types or is/as tests instead of respondsToSelector:, and use closures wherever you can instead of performSelector: for better type/memory safety.)

But there are still a number of important ObjC-based APIs that use selectors, including timers and the target/action pattern. Swift provides the Selector type for working with these. (Swift automatically uses this in place of ObjC's SEL type.)

In Swift 2.2 (Xcode 7.3) and later (including Swift 3 / Xcode 8 and Swift 4 / Xcode 9):

You can construct a Selector from a Swift function type using the #selector expression.

let timer = Timer(timeInterval: 1, target: object,
selector: #selector(MyClass.test),
userInfo: nil, repeats: false)
button.addTarget(object, action: #selector(MyClass.buttonTapped),
for: .touchUpInside)
view.perform(#selector(UIView.insertSubview(_:aboveSubview:)),
with: button, with: otherButton)

The great thing about this approach? A function reference is checked by the Swift compiler, so you can use the #selector expression only with class/method pairs that actually exist and are eligible for use as selectors (see "Selector availability" below). You're also free to make your function reference only as specific as you need, as per the Swift 2.2+ rules for function-type naming.

(This is actually an improvement over ObjC's @selector() directive, because the compiler's -Wundeclared-selector check verifies only that the named selector exists. The Swift function reference you pass to #selector checks existence, membership in a class, and type signature.)

There are a couple of extra caveats for the function references you pass to the #selector expression:

  • Multiple functions with the same base name can be differentiated by their parameter labels using the aforementioned syntax for function references (e.g. insertSubview(_:at:) vs insertSubview(_:aboveSubview:)). But if a function has no parameters, the only way to disambiguate it is to use an as cast with the function's type signature (e.g. foo as () -> () vs foo(_:)).
  • There's a special syntax for property getter/setter pairs in Swift 3.0+. For example, given a var foo: Int, you can use #selector(getter: MyClass.foo) or #selector(setter: MyClass.foo).

General notes:

Cases where #selector doesn't work, and naming: Sometimes you don't have a function reference to make a selector with (for example, with methods dynamically registered in the ObjC runtime). In that case, you can construct a Selector from a string: e.g. Selector("dynamicMethod:") — though you lose the compiler's validity checking. When you do that, you need to follow ObjC naming rules, including colons (:) for each parameter.

Selector availability: The method referenced by the selector must be exposed to the ObjC runtime. In Swift 4, every method exposed to ObjC must have its declaration prefaced with the @objc attribute. (In previous versions you got that attribute for free in some cases, but now you have to explicitly declare it.)

Remember that private symbols aren't exposed to the runtime, too — your method needs to have at least internal visibility.

Key paths: These are related to but not quite the same as selectors. There's a special syntax for these in Swift 3, too: e.g. chris.valueForKeyPath(#keyPath(Person.friends.firstName)). See SE-0062 for details. And even more KeyPath stuff in Swift 4, so make sure you're using the right KeyPath-based API instead of selectors if appropriate.

You can read more about selectors under Interacting with Objective-C APIs in Using Swift with Cocoa and Objective-C.

Note: Before Swift 2.2, Selector conformed to StringLiteralConvertible, so you might find old code where bare strings are passed to APIs that take selectors. You'll want to run "Convert to Current Swift Syntax" in Xcode to get those using #selector.

NSMethodSignature in Swift

Getting a method signature is rather easy:

var target = NSObject()
var selector = Selector("success")

var signature: NSMethodSignature? = target.methodSignatureForSelector(selector)

However, using NSInvocation is forbidden:

NSInvocation not available

Rethink your approach.

NSDocument callback method signature

There is one colon missing in the selector. It should be

Selector("document:didSave:contextInfo:")

How to set an action for an UIBarButtonItem?

Select the UIBarButton , Create an IBAction by CNRL+Dragging at the implementation section in .m file then try to print with NSLog use breakpoint to know if the control is reaching the IBAction you created.

Passing arguments to selector in Swift

It looks like you're misunderstanding a couple of things.

When using target/action, the function signature has to have a certain form…

func doSomething()

or

func doSomething(sender: Any)

or

func doSomething(sender: Any, forEvent event: UIEvent)

where…

The sender parameter is the control object sending the action message.

In your case, the sender is the UITapGestureRecognizer

Also, #selector() should contain the func signature, and does NOT include passed parameters. So for…

func handleTap(sender: UIGestureRecognizer) {

}

you should have…

let gesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:)))

Assuming the func and the gesture are within a view controller, of which modelObj is a property / ivar, there's no need to pass it with the gesture recogniser, you can just refer to it in handleTap

performSelector may cause a leak because its selector is unknown

Solution

The compiler is warning about this for a reason. It's very rare that this warning should simply be ignored, and it's easy to work around. Here's how:

if (!_controller) { return; }
SEL selector = NSSelectorFromString(@"someMethod");
IMP imp = [_controller methodForSelector:selector];
void (*func)(id, SEL) = (void *)imp;
func(_controller, selector);

Or more tersely (though hard to read & without the guard):

SEL selector = NSSelectorFromString(@"someMethod");
((void (*)(id, SEL))[_controller methodForSelector:selector])(_controller, selector);

Explanation

What's going on here is you're asking the controller for the C function pointer for the method corresponding to the controller. All NSObjects respond to methodForSelector:, but you can also use class_getMethodImplementation in the Objective-C runtime (useful if you only have a protocol reference, like id<SomeProto>). These function pointers are called IMPs, and are simple typedefed function pointers (id (*IMP)(id, SEL, ...))1. This may be close to the actual method signature of the method, but will not always match exactly.

Once you have the IMP, you need to cast it to a function pointer that includes all of the details that ARC needs (including the two implicit hidden arguments self and _cmd of every Objective-C method call). This is handled in the third line (the (void *) on the right hand side simply tells the compiler that you know what you're doing and not to generate a warning since the pointer types don't match).

Finally, you call the function pointer2.

Complex Example

When the selector takes arguments or returns a value, you'll have to change things a bit:

SEL selector = NSSelectorFromString(@"processRegion:ofView:");
IMP imp = [_controller methodForSelector:selector];
CGRect (*func)(id, SEL, CGRect, UIView *) = (void *)imp;
CGRect result = _controller ?
func(_controller, selector, someRect, someView) : CGRectZero;

Reasoning for Warning

The reason for this warning is that with ARC, the runtime needs to know what to do with the result of the method you're calling. The result could be anything: void, int, char, NSString *, id, etc. ARC normally gets this information from the header of the object type you're working with.3

There are really only 4 things that ARC would consider for the return value:4

  1. Ignore non-object types (void, int, etc)
  2. Retain object value, then release when it is no longer used (standard assumption)
  3. Release new object values when no longer used (methods in the init/ copy family or attributed with ns_returns_retained)
  4. Do nothing & assume returned object value will be valid in local scope (until inner most release pool is drained, attributed with ns_returns_autoreleased)

The call to methodForSelector: assumes that the return value of the method it's calling is an object, but does not retain/release it. So you could end up creating a leak if your object is supposed to be released as in #3 above (that is, the method you're calling returns a new object).

For selectors you're trying to call that return void or other non-objects, you could enable compiler features to ignore the warning, but it may be dangerous. I've seen Clang go through a few iterations of how it handles return values that aren't assigned to local variables. There's no reason that with ARC enabled that it can't retain and release the object value that's returned from methodForSelector: even though you don't want to use it. From the compiler's perspective, it is an object after all. That means that if the method you're calling, someMethod, is returning a non object (including void), you could end up with a garbage pointer value being retained/released and crash.

Additional Arguments

One consideration is that this is the same warning will occur with performSelector:withObject: and you could run into similar problems with not declaring how that method consumes parameters. ARC allows for declaring consumed parameters, and if the method consumes the parameter, you'll probably eventually send a message to a zombie and crash. There are ways to work around this with bridged casting, but really it'd be better to simply use the IMP and function pointer methodology above. Since consumed parameters are rarely an issue, this isn't likely to come up.

Static Selectors

Interestingly, the compiler will not complain about selectors declared statically:

[_controller performSelector:@selector(someMethod)];

The reason for this is because the compiler actually is able to record all of the information about the selector and the object during compilation. It doesn't need to make any assumptions about anything. (I checked this a year a so ago by looking at the source, but don't have a reference right now.)

Suppression

In trying to think of a situation where suppression of this warning would be necessary and good code design, I'm coming up blank. Someone please share if they have had an experience where silencing this warning was necessary (and the above doesn't handle things properly).

More

It's possible to build up an NSMethodInvocation to handle this as well, but doing so requires a lot more typing and is also slower, so there's little reason to do it.

History

When the performSelector: family of methods was first added to Objective-C, ARC did not exist. While creating ARC, Apple decided that a warning should be generated for these methods as a way of guiding developers toward using other means to explicitly define how memory should be handled when sending arbitrary messages via a named selector. In Objective-C, developers are able to do this by using C style casts on raw function pointers.

With the introduction of Swift, Apple has documented the performSelector: family of methods as "inherently unsafe" and they are not available to Swift.

Over time, we have seen this progression:

  1. Early versions of Objective-C allow performSelector: (manual memory management)
  2. Objective-C with ARC warns for use of performSelector:
  3. Swift does not have access to performSelector: and documents these methods as "inherently unsafe"

The idea of sending messages based on a named selector is not, however, an "inherently unsafe" feature. This idea has been used successfully for a long time in Objective-C as well as many other programming languages.


1 All Objective-C methods have two hidden arguments, self and _cmd that are implicitly added when you call a method.

2 Calling a NULL function is not safe in C. The guard used to check for the presence of the controller ensures that we have an object. We therefore know we'll get an IMP from methodForSelector: (though it may be _objc_msgForward, entry into the message forwarding system). Basically, with the guard in place, we know we have a function to call.

3 Actually, it's possible for it to get the wrong info if declare you objects as id and you're not importing all headers. You could end up with crashes in code that the compiler thinks is fine. This is very rare, but could happen. Usually you'll just get a warning that it doesn't know which of two method signatures to choose from.

4 See the ARC reference on retained return values and unretained return values for more details.



Related Topics



Leave a reply



Submit