How to Pass Closure as a Parameter in Perform(Selector, Withobject)

How to pass closure as a parameter in perform(selector, withObject)

Your closure is not an Objective-C block, so it can't be passed through the ObjC runtime. You have to mark it as a block using @convention.

let closure: @convention(block) () -> Void = { ... }

You can convert an existing closure into a block by assigning it:

let closure = { ... }
let block: @convention(block) () -> Void = closure
perform(#selector(foo(param:)), with: block)

Objective-C blocks are actually objects, and participate in ARC. The crash occurs because perform tries to call Block_copy on a non-block.

Of course, as a rule selectors are not the right tools in Swift, and you should convert any selector-based interface to just take a function argument in the first place. If you find yourself using perform, you're probably on the wrong road in Swift. But it is still available if you need it.

How to pass block arguments through performSelector?

If you look at the documentation for perform it says:

This method is the same as perform(_:) except that you can supply an argument for aSelector. aSelector should identify a method that takes a single argument of type id.

id is a pointer to an objective-c object. From Swift, you are going to be limited to passing objects that inherit from NSObject. A closure does not meet those requirements. If you must, you can do something like so:

class Block: NSObject {
let block: () -> Void
init(block: @escaping () -> Void) {
self.block = block
}
}

class SomeClass: NSObject {
@objc func foo() {
print("foo...")
let selectorName = "bar:"
let selector = Selector(selectorName)
if self.responds(to: selector) {
let block = Block {
print("block")
}
self.perform(selector, with: block)
}
}

@objc func bar(_ arg1: Block) {
print("bar...")
arg1.block()
}
}

Passing closures to Private API's

First.. How I found the correct completion block signature: http://i.imgur.com/UGVayPE.png

That shows that it allocates an NSMutableArray as the parameter to the completion block when it invokes it. That's the only parameter. You don't have to do this (disassemble it). Upon an exception being thrown, you can print the signature. Sometimes it will also tell you which kind of block is expected.


Next, my opinion on invoking selectors dynamically..

Your best option is to not perform selectors.. It's a pain especially when the call contains MULTIPLE parameters..

What you can do is invocation through interface/extension pointers.. I do this in C++ (Idea from the Pimpl idiom.. COMM interfaces do this too) all the time and it works with Swift, Objective-C, Java.. etc..

Create a protocol that has the same interface as the object. Create an extension that inherits that protocol. Then cast the object instance to that extension/interface/protocol.

Call whatever function you want via the interface/extension/protocol pointer.

import UIKit
import MediaPlayer

@objc
protocol MPAProtocol { //Functions must be optional. That way you don't implement their body when you create the extension.
optional func availableRoutes() -> NSArray
optional func discoveryMode() -> Int
optional func fetchAvailableRoutesWithCompletionHandler(completion: (routes: NSArray) -> Void)
optional func name() -> NSString
}

extension NSObject : MPAProtocol { //Needed otherwise casting will fail!

//Do NOT implement the body of the functions from the protocol.
}

Usage:

let MPAVRoutingControllerClass: NSObject.Type = NSClassFromString("MPAVRoutingController") as! NSObject.Type
let MPAVRoutingController: MPAProtocol = MPAVRoutingControllerClass.init() as MPAProtocol

MPAVRoutingController.fetchAvailableRoutesWithCompletionHandler! { (routes) in
print(routes);
}

If you were to do it with a Bridging header instead of creating the extension + protocol, you'd just do a single Objective-C category:

#import <Foundation/Foundation.h>

@interface NSObject (MPAVRoutingControllerProtocol)
- (void)fetchAvailableRoutesWithCompletionHandler:(void(^)(NSArray *routes))completion;
@end

@implementation NSObject (MPAVRoutingControllerProtocol)

@end

Then:

let MPAVRoutingControllerClass: NSObject.Type = NSClassFromString("MPAVRoutingController") as! NSObject.Type
let MPAVRoutingController = MPAVRoutingControllerClass.init()

MPAVRoutingController.fetchAvailableRoutesWithCompletionHandler! { (routes) in
print(routes);
}

Finally, if you can use protocol injection, you can do this much easier:

func classFromString(cls: String, interface: Protocol?) -> NSObject.Type? {
guard let interface = interface else {
return NSClassFromString(cls) as? NSObject.Type
}

if let cls = NSClassFromString(cls) {
if class_conformsToProtocol(cls, interface) {
return cls as? NSObject.Type
}

if class_addProtocol(cls, interface) {
return cls as? NSObject.Type
}
}
return nil
}

func instanceFromString<T>(cls: String, interface: Protocol?) -> T? {
return classFromString(cls, interface: interface)?.init() as? T
}

@objc
protocol MPAProtocol {
optional func availableRoutes() -> NSArray
optional func discoveryMode() -> Int
optional func fetchAvailableRoutesWithCompletionHandler(completion: (routes: NSArray) -> Void)
optional func name() -> NSString
}

let MPAVRoutingController: MPAProtocol = instanceFromString("MPAVRoutingController", interface: MPAProtocol.self)!

MPAVRoutingController.fetchAvailableRoutesWithCompletionHandler! { (routes) in
print(routes);
}

When Invoke perform selector to excute function with Int param in swift , I get a unexpected result

Try:

func test() {
let sel2 = #selector(newAction(pra:param:))
perform(sel2, with: NSNumber(value: 2), with: "bbb")
}

@objc func newAction(pra: NSNumber, param: String) {
print("pra = \(pra.intValue), param = \(param)")
// or just
print("pra = \(pra), param = \(param)")
}

Should also work:

func test() {
let sel2 = #selector(newAction(pra:param:))
perform(sel2, with: 2, with: "bbb")
}

@objc func newAction(pra: NSNumber, param: String) {
print("pra = \(pra), param = \(param)")
}

The argument type needs to be a reference type. In Swift side, passing an Int to withObject: parameter makes that Int converted into NSNumber.
But the Objective-C runtime does not automatically convert it back to Int. (Remember that Objective-C selectors do not have type information.)

Passing parameters to addTarget:action:forControlEvents

action:@selector(switchToNewsDetails:)

You do not pass parameters to switchToNewsDetails: method here. You just create a selector to make button able to call it when certain action occurs (touch up in your case). Controls can use 3 types of selectors to respond to actions, all of them have predefined meaning of their parameters:

  1. with no parameters

    action:@selector(switchToNewsDetails)
  2. with 1 parameter indicating the control that sends the message

    action:@selector(switchToNewsDetails:)
  3. With 2 parameters indicating the control that sends the message and the event that triggered the message:

    action:@selector(switchToNewsDetails:event:)

It is not clear what exactly you try to do, but considering you want to assign a specific details index to each button you can do the following:

  1. set a tag property to each button equal to required index
  2. in switchToNewsDetails: method you can obtain that index and open appropriate deatails:

    - (void)switchToNewsDetails:(UIButton*)sender{
    [self openDetails:sender.tag];
    // Or place opening logic right here
    }

@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.

Closures definition and usage in Objective-C

Is this the better way to handle this?

Depending on your usage, you may want to store this as an instance variable (unless you really want the block to be shared between classes, and thus changed for all classes anytime startEvent: is called).

Is a __block an NSObject? a C declaration?

It's a C storage qualifier.

Why do I have to copy it? Is it not possible just to retain it?

You can retain it, but it probably won't do what you want it to do. In C and Objective-C, blocks are created on the stack. When you copy it, it (and any variables it closed over) are copied to the heap. If you retain it, it and its variables are not copied to the heap.

If it's not an Obj-C Object, why I can call to [... copy] and [... retain] as it were?

Magic. :) More seriously, in Objective-C, blocks are designed to act as Objective-C objects. (It's similar to the reason you can seamlessly cast between NSString and CFStringRef -- the objects are designed to make this possible.)



Related Topics



Leave a reply



Submit