Search code examples
swiftmethodsprotocolsoption-type

Accessing optional method in protocol returns scope error


I've exhausted every SO thread about this topic and haven't gotten anywhere with this error. Code Setup:

@objc protocol MyProtocol: NSObjectProtocl{
    @objc optional func myFunction()
}

class MyClass: NSObject, MyProtocol {
    func doSomething() {
        myFunction?() //Error: cannot find myFunction in scope.
        self.myFunction() //Error: Value of type 'MyClass' has no member 'myFunction'
    }
}

Things I've tried:

  • Using responds(to:) to check selector. The same error pops up when calling the function afterward.
if self.responds(to: #selector(MyProtocol.myFunction)){
    self.myFunction?() //Error: Value of type 'MyClass' has no member 'myFunction'
}
  • Using if let to check if the function is implemented runs into the same error above.
  • Giving the optional function a return type @objc optional func myFunction() -> NSError?
  • Using a default implementation works, but doesn't allow me to use the @objc tag.
extension MyProtocol  {
    @objc func myFunction(){ //Error: @objc can only be used with members of classes, @objc protocols, and concrete extensions of classes.
    }
}

Other Objective-C implementations that use MyClass need myFunction, so I need to expose myFunction with @objc.

Do I have to implement myFunction inside MyClass if I want to use it. This problem is when converting a file from Obj-C to Swift, where Obj-C allows calling optional methods without having to check their implementation.


Solution

  • class MyClass: NSObject, MyProtocol { ... }
    

    This says that MyClass implements MyProtocol, which allows, but does not require myFunction. The compiler can clearly see that MyClass does not implement myFunction, so self.myFunction() isn't a thing. It could be a thing, but it's clearly not a thing. The protocol doesn't play into this.

    You could "work around" this by saying "I don't mean MyClass; I mean MyProtocol":

    func doSomething() {
        (self as MyProtocol).myFunction?()
    }
    

    But this is kind of silly IMO. self doesn't implement myFunction and this is known at compile time.

    If the point is to implement this via subclassing, the correct way to do that is to just implement it as empty in the superclass:

    class MyClass: NSObject, MyProtocol {
        func myFunction() {}
    
        func doSomething() {
            myFunction()
        }
    }
    

    This is precisely equivalent to "do nothing" while allowing overrides in subclasses.