How to use @objc protocol with optional and extensions at the same time?

本小妞迷上赌 提交于 2019-12-22 18:44:05

问题


This code does not compile and might sound stupid as it is, but i'll explain why it's so important!

@objc protocol p {
    optional func f1()
    func f2()
}

extension p {
    func f1() { }
    func f2() { }
}


class foo: p {
}

Compiler says Type c does not conform to protocol 'p' and that's maybe because you can not use @objc optional and extensions at the same time (and does not make sence in this scenario either). But consider the following example:

I want to set a selector on a non-optional method defined in protocol in my extension (main reason i used @objc):

func f1() { } -> func f1() { ... #selector(Self.f2) ... }

And i also want my f2() function to have default behaviour. If i mark f2() as optional, it can not be used in #selector because compiler does not know if this method actually exists in the case of need. Sure there're lots of nasty workarounds like global methods, sending Selectors to methods as input and etc, but is there a clean way to achieve it?


This is the practical issue

@objc
protocol Refreshable {
    weak var refreshControl: UIRefreshControl? { get set }
    optional func setupRefreshControl()
    func refresh()
}

@objc
protocol ContentLoader {
    func load(reset: Bool)
}

extension Refreshable where Self: ContentLoader {
    func refresh() {
        delay(0.75) { [weak self] in
            self?.load(true)
        }
    }
}

extension Refreshable where Self: UICollectionViewController {
    func setupRefreshControl() {
        let newRefreshControl = UIRefreshControl()

        newRefreshControl.tintColor = UIColor.grayColor()
        newRefreshControl.addTarget(self, action: #selector(Self.refresh), forControlEvents: .ValueChanged)
        collectionView?.addSubview(newRefreshControl)
        refreshControl = newRefreshControl
    }
}

Now if a ViewController implements Refreshable and ContentLoader, it does not find the default refresh function, but it does find setupRefreshControl. So i figured let's mark refresh as optional too, but by doing that, you can not send it to selector any more.

I even tried this:

func refresh() -> optional func refresh() and

let str = "refresh"
let sel = Selector(str)

It silents the compiler yes, but does not work either... rises unrecognized selector sent to instance....


回答1:


I think this is not possible in swift (because of the way it bridges to @objc protocols). But this is a work around(using Obj-c associated objects) to solve the unrecognized selector sent to instance... problem.

fileprivate class AssociatedObject: NSObject {
    var closure: (() -> ())? = nil

    func trigger() {
        closure?()
    }
}

// Keys should be global variables, do not use, static variables inside classes or structs.
private var associatedObjectKey = "storedObject"

protocol CustomProtocol: class {
    func setup()
}

extension CustomProtocol where Self: NSObject {

    fileprivate var associatedObject: AssociatedObject? {
        get {
            return objc_getAssociatedObject(self, &associatedObjectKey) as? AssociatedObject
        }

        set {
            objc_setAssociatedObject(self, &associatedObjectKey, newValue, .OBJC_ASSOCIATION_RETAIN)
        }
    }


    func setup() {
        let object = AssociatedObject()
        object.closure = { [weak self] in // Do not forget to use weak in order to avoid retain-cycle
            self?.functionToCallIndirectlyWithSelector()
        }

        let selector = #selector(object.trigger)
//      Uncomment next line to test it's functionality
        object.perform(selector)

//      Here, you must add selector to the target which needs to call the selector, for example:
//      refreshControl.addTarget(object, action: selector, forControlEvents: .valueChanged)

        self.associatedObject = object
    }

    func functionToCallIndirectlyWithSelector() {
        print("Function got called indirectly.")
    }
}


class CustomClass: NSObject, CustomProtocol {}

let instance = CustomClass()

instance.setup()

I added Self: NSObject constraint to be able to test it's functionality in playground, I'm not sure if it's necessary or not.



来源:https://stackoverflow.com/questions/38190702/how-to-use-objc-protocol-with-optional-and-extensions-at-the-same-time

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!