Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use #selector(myMethodName) in a protocol extension?

protocol LazyUpdateable {
    func waitToDoStuff()
    func myMethodName()
}


extension LazyUpdateable where Self: NSObject {
    func waitToDoStuff() {
        self.performSelector(#selector(myMethodName), withObject: nil, afterDelay: 1.5)
    }

    func myMethodName() {

    }
}

With this update i get the error Argument of #selector refers to a method that is not exposed to objective c, but if i go with the old Selector("myMethodName") i get a warning to change to the better way of doing it. Is it possible to use the #selector() in this case? It won't work with setting @objc on my protocol, i've tried it.

Here is a playground you can try that shows it does not work with setting @objc

import Foundation
import UIKit
import XCPlayground


@objc protocol LazyUpdatable {
    optional func waitToDoStuff()
    optional func myMethodName()
}

extension LazyUpdatable where Self: UIViewController {
    func waitToDoStuff() {
        self.performSelector(#selector(myMethodName), withObject: nil, afterDelay: 1.5)
    }

    func myMethodName() {
        print("LOL")
    }
}


@objc
class AViewController: UIViewController, LazyUpdatable {
    func start() {
        waitToDoStuff()
    }
}

let aViewController = AViewController()
aViewController.start()

XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
like image 363
bogen Avatar asked Mar 23 '16 09:03

bogen


2 Answers

Dynamic dispatching using #selector() or Selector() doesn't see your Swift protocol extension. Instead, try avoiding Objective-C altogether, if possible. You can achieve the same result using libdispatch:

protocol LazyUpdatable {

    func waitToDoStuff()
    func myMethodName()
}

extension LazyUpdatable {

    func waitToDoStuff() {
        let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(1.5 * Double(NSEC_PER_SEC)))
        dispatch_after(dispatchTime, dispatch_get_main_queue()) {
            self.myMethodName()
        }
    }

    func myMethodName() {
        print("Aloha!")
    }
}

class ViewController: UIViewController, LazyUpdatable {

    override func viewDidLoad() {
        super.viewDidLoad()
        waitToDoStuff()
    }
}

Granted, this is not as flexible as using selectors, but lets you use real Swift protocol extensions.

EDIT: If you want to be able to cancel invoking the method call, try the following:

var lazyUpdatableCancelKey = UInt8(0)

protocol LazyUpdatable: class {

    func waitToDoStuff()
    func cancelDoingStuff()
    func myMethodName()
}

extension LazyUpdatable {

    func waitToDoStuff() {
        let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(1.5 * Double(NSEC_PER_SEC)))
        dispatch_after(dispatchTime, dispatch_get_main_queue()) {
            if let shouldCancel = objc_getAssociatedObject(self, &lazyUpdatableCancelKey) as? Bool where shouldCancel == true {
                return
            }
            self.myMethodName()
        }
    }

    func cancelDoingStuff() {
        objc_setAssociatedObject(self, &lazyUpdatableCancelKey, true, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    }

    func myMethodName() {
        print("Aloha!")
    }
}

class ViewController: UIViewController, LazyUpdatable {

    override func viewDidLoad() {
        super.viewDidLoad()
        waitToDoStuff()

        let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(1.4 * Double(NSEC_PER_SEC)))
        dispatch_after(dispatchTime, dispatch_get_main_queue()) {
            self.cancelDoingStuff()
        }
    }
}
like image 142
Eivind Rannem Bøhler Avatar answered Oct 04 '22 22:10

Eivind Rannem Bøhler


For XCode 8.2.1 and Swift 3.

You you can use the traditional Selector("methodName") like you have tried. Additionally you can silence the warning by wrapping it in parenthesis Selector(("methodName"))

Since everything is within your protocol it's less likely you'll make typos that will end up crashing

like image 35
wyu Avatar answered Oct 04 '22 21:10

wyu