Значение типа «X» не имеет члена «y» — необязательная функция в протоколе.

Я пытаюсь лучше понять протоколы в Swift. В частности, необязательные методы протокола. Я думал, что проблема может быть связана с тем, что мой протокол определяется/используется в другом файле, но если вы поместите следующее на игровую площадку, вы получите ту же проблему:

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()

Вот сообщение об ошибке:

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

По какой-то причине он не принимает, что протокол был определен в NSObject. Автодополнение находит его, но компилятор не пропускает.

Я не уверен, что моя часть ?? true будет работать, но я хочу, чтобы это было значение по умолчанию, если метод не определен.

Как заставить это работать?


person RyanJM    schedule 24.02.2016    source источник


Ответы (3)


Я думаю, это потому, что компилятор знает, что NSObject не имеет метода shouldJump, поэтому вызов object.shouldJump?() не имеет смысла. Вы можете привести object к своему протоколу:

let jump = (object as MyProtocol).shouldJump?() ?? true
person Cosyn    schedule 24.02.2016

Ваш NSObject соответствует MyProtocol, но поскольку он не реализует необязательный метод протокола, компилятор знает, что у него нет Selector shouldJump:

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

Один из способов решить эту проблему — реализовать метод протокола в расширении, чтобы объект выполнял этот селектор:

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

Если вы не хотите реализовывать необязательный метод в расширении, вы должны привести свой NSObject к типу MyProtocol и убедиться, что он отвечает на необязательный 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")
        }


    }
}

Вы также можете пропустить шаг respondsToSelector и использовать if let или guard, чтобы убедиться, что shouldJump() возвращает ненулевое значение.

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")
        }
    }
}
person JAL    schedule 24.02.2016
comment
Начиная с бета-версии Xcode версии 7.3 (7D152p), использование строковых литералов в качестве селектора устарело. Эквивалентный вызов теперь является object.respondsToSelector(#selector(MyProtocol.shouldJump)). - person Price Ringo; 24.02.2016
comment
@PriceRingo Спасибо, я еще не использую бета-версию, поэтому я не реализовал это изменение. Когда это потребуется для последней стабильной версии Xcode, я обновлю этот ответ. - person JAL; 24.02.2016
comment
Не могли бы вы написать код и для вашего последнего варианта? Как человек с многолетним опытом работы с Objective-C, но еще не очень разбирающийся в Swift, это кажется наиболее интересным подходом. :) - person Steven Fisher; 24.02.2016
comment
@StevenFisher Конечно, я обновлю это, когда вернусь к своему компьютеру завтра (сейчас на мобильном телефоне). - person JAL; 24.02.2016
comment
@StevenFisher обновил мой ответ, включив код Swift для последнего варианта. - person JAL; 24.02.2016
comment
Спасибо большое. Кроме того, кто, черт возьми, проголосовал бы за это? - person Steven Fisher; 25.02.2016

Swift — типобезопасный язык. Чтобы иметь возможность использовать shouldJump?(), вы сначала должны иметь объект, совместимый с MyProtocol. В этом случае вы можете просто указать свой тип:

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

Вы также можете сохранить его в переменной:

let jumper = object as MyProtocol
let jump = jumper?.shouldJump() ?? true
person fpg1503    schedule 24.02.2016