How to use performSelector with closure argument

Hi, I am suffering from one problem.
In swift 4.2, I tried to perform below code.

class A: NSObject {
    func performClosure(closure: @escaping () -> ()) {
        perform(#selector(performClosureOnClientThread(_:)), with: closure)
    }

    @objc func performClosureOnClientThread(_ closure: () -> ()) {
        closure()
    }
}

and main

let a = A()
let closure = {
    print(good)
}

a.performClosure(closure) 

but, It occurred crash.

Ultimately, I want to execute the selector using execute perform(: on:with:waitUntilDone:modes) on the executed thread. Can't I use the method('perform') in swift?

This is a tricky problem. It's happening because perform(_:with:) and Selector use the Objective-C runtime. The Objective-C runtime does not check types strictly like Swift does.

The perform(_:with:) method of NSObject takes two arguments: a Selector and an Any!.

Swift will automatically convert an object of type () -> () (the type of closure) to Any! for you. All Selectors have the same type, so Swift accepts #selector(performClosureOnClientThread(_:)) for the Selector argument.

However, perform(_:with:) expects that the Selector selects a method of type (Any!) -> Unmanaged<AnyObject>!.

What is the type of your performClosureOnClientThread(_:) method? Its type is (() -> ()) -> () (takes a closure, returns Void).

Is type (() -> ()) -> () the same as type (Any!) -> Unmanaged<AnyObject>!? No, they are not the same. In particular, () -> () is passed in a different way than Any!. When your performClosureOnClientThread method tries to use the closure argument, it fails, because it wasn't passed a () -> ().

To fix the problem,, change your performClosureOnClientThread(_:) method to have the type that perform(_:with:) expects.

@objc func performClosureOnClientThread(_ closure: Any!) -> Unmanaged<AnyObject>! {
    if let closure = closure as? () -> () {
        closure()
    }
    return nil
}
2 Likes

Also, I think using perform(_:on:with:waitUntilDone:modes:) is suspicious. It only works for a thread that runs a Runloop, and usually only the main thread runs a RunLoop. You might be better off using a DispatchQueue or an OperationQueue.

If you describe your goal at a higher level, we might be able to give you better advice.

1 Like

Thank you! you are savior for me.