Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Value of type 'X' has no member 'y' - optional func in protocol

I'm trying to get a better understanding of protocols in Swift. Specifically optional protocol methods. I thought the issue might have to do with my protocol being defined / used in a different file, but if you put the following in a playground you'll get the same issue:

import Foundation

@objc protocol MyProtocol {
    optional func shouldJump() -> Bool
}

extension NSObject : MyProtocol {}

class Test {
    func testJump() {
        let object = NSObject()
        let jump = object.shouldJump?() ?? true

        print("should jump: \(jump)")
    }
}

let t = Test()
t.testJump()

Here is the error message:

error: value of type 'NSObject' has no member 'shouldJump'
            let jump = object.shouldJump?() ?? true
                       ^~~~~~ ~~~~~~~~~~

For some reason it doesn't accept that the protocol has been defined on NSObject. Code completion finds it, but the compiler doesn't let it pass.

I'm not sure if my ?? true part will work, but I want that to be a default value incase the method isn't defined.

How do I get this to work?

like image 410
RyanJM Avatar asked Feb 24 '16 01:02

RyanJM


Video Answer


3 Answers

Your NSObject conforms to MyProtocol, but because it doesn't implement the optional protocol method, the compiler knows it does not have the Selector shouldJump:

let object = NSObject()
object.conformsToProtocol(MyProtocol) // true
object.respondsToSelector("shouldJump") // false

One way to solve this is to implement the protocol method in the extension in order for the object to perform that selector:

extension NSObject : MyProtocol {
    func shouldJump() -> Bool {
        // some logic here
        return true
    }
}

class Test {
    func testJump() {
        let object = NSObject()

        let jump = object.shouldJump()

        print("should jump: \(jump)")
    }
}

let t = Test()
t.testJump() // works

If you don't want to implement the optional method in the extension, you have to cast your NSObject as MyProtocol and verify that it responds to the optional Selector:

class Test {
    func testJump() {
        let object = NSObject()

        let obj = object as MyProtocol

        if object.respondsToSelector("shouldJump")  {
            let jump = obj.shouldJump?()

            print("should jump: \(jump)")
        } else {
            print("nope")
        }


    }
}

You can also skip the respondsToSelector step and use an if let or guard to verify that shouldJump() returns non-nil.

class Test {
    func testJump() {
        let object = NSObject()

        guard let obj: MyProtocol = object else {
            return // object does not conform to MyProtocol
        }

        if let jump = obj.shouldJump?() { // if shouldJump() returns non-nil
            print("should jump: \(jump)")
        } else {
            print("nope")
        }
    }
}
like image 185
JAL Avatar answered Oct 16 '22 16:10

JAL


I think this is because the compiler knowns NSObject doesn't have shouldJump method, so the call object.shouldJump?() makes no sense. You can cast object to your protocol:

let jump = (object as MyProtocol).shouldJump?() ?? true
like image 1
Cosyn Avatar answered Oct 16 '22 15:10

Cosyn


Swift is a type safe language. In order to be able to use shouldJump?() you first must have an object conformant to MyProtocol. In this case you can simply cast your type:

let jump = (object as MyProtocol).shouldJump?() ?? true

You can also store it in a variable:

let jumper = object as MyProtocol
let jump = jumper?.shouldJump() ?? true
like image 1
fpg1503 Avatar answered Oct 16 '22 16:10

fpg1503