Swift: использование расширения протокола приводит к "нераспознанному селектору, отправленному экземпляру"

Я пытаюсь добавить функцию касания ко всем UIViewControllers, где они соответствуют протоколу MyProtocol,

Вот как я это делаю:

import UIKit

protocol MyProtocol: class{
    var foo: String? {get set}
    func bar()
}


extension MyProtocol where Self: UIViewController {
    func bar() {
        print(foo)
    }
}


class TestViewController: UIViewController, MyProtocol{
    var foo: String?

    override func viewDidLoad() {
        super.viewDidLoad()

        foo = "testing"
        let tapGesture = UITapGestureRecognizer(target: self, action: "bar")
}

Что приводит к следующему при нажатии на экран:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: unrecognized selector sent to instance

Я понимаю ошибку, но не знаю, как ее исправить. Кто-нибудь может подсказать, как это можно сделать?

2 ответа

Решение

Проблема в том, что Objective-C ничего не знает о расширениях протокола. Таким образом, вы не можете использовать расширение протокола для внедрения метода в класс таким образом, чтобы механизм обмена сообщениями Objective-C мог его увидеть. Вы должны объявить bar в самом классе контроллера вида.

(Я понимаю, что это именно то, что вы пытались избежать, но я не могу с этим поделать.)

Своего рода обходной путь может быть таким:

override func viewDidLoad() {
    super.viewDidLoad()

    foo = "testing"
    let tapGesture = UITapGestureRecognizer(target: self, action: "baz")
    self.view.addGestureRecognizer(tapGesture)
}

func baz() {
    self.bar()
}

Теперь мы используем механизм обмена сообщениями Objective-C для вызова baz а затем механизм обмена сообщениями Свифта, чтобы позвонить bar и, конечно, это работает. Я понимаю, что это не так чисто, как вы имели в виду, но, по крайней мере, сейчас реализация bar может жить в расширении протокола.

(Конечно, другим решением было бы сделать то, что мы делали до появления расширений протокола: сделать так, чтобы все ваши контроллеры представления наследовали от некоторого общего пользовательского подкласса UIViewController, содержащего bar.)

Ответ Мэтта правильный. Вы можете играть на своей игровой площадке, чтобы увидеть, как именно это работает. В следующем фрагменте вы можете увидеть, что даже если какой-то объект может выполнять селектор objc, если он не используется должным образом, он может привести к сбою вашего кода:-)

//: Playground - noun: a place where people can play
import Foundation
protocol Bar {}
extension Bar {
    func bar()->Self {
        print("bar from protocol Bar extension")
        dump(self)
        return self
    }
}
class C: NSObject {
    func dummy() {
        print("dummy from C")
        dump(self)
    }
}
extension NSObject {
    func foo()->NSObject {
        print("foo from NSObject extension")
        dump(self)
        return self
    }
}
let c = C()
c.respondsToSelector("foo")     // true
c.respondsToSelector("dummy")   // true
c.foo()
let i = c.performSelector("foo")// prints the same and DON'T crash
c.dummy()   // works
//c.performSelector("dummy")    // if uncommented then
/* prints as expected
dummy from C
▿ C #0
  - super: <__lldb_expr_517.C: 0x7fcca0c05a60>
*/
// and crash!!! because performSelector returns Unmanaged<AnyObject>!
// and this is not what we return from dummy ( Void )

let o = NSObject()
o.foo()
extension NSObject: Bar {}
// try to uncomment this extension and see which version is called in following lines
/*
extension NSObject {
    func bar() -> Self {
        print("bar implemented in NSObject extension")
        return self
    }
}
*/

c.bar() // bar protocol extension Bar is called here
o.bar() // and here

o.respondsToSelector("foo") // true
o.respondsToSelector("bar") // false as expected ( see matt's answer !! )
o.performSelector("foo")    // returns Unmanaged<AnyObject>(_value: <NSObject: 0x7fc40a541270>)

Существует большая ошибка с executeSelector: и friends: его можно использовать только с аргументами и возвращаемыми значениями, которые являются объектами! Так что, если у вас есть что-то, например, логическое значение или возвращаемое значение void, вам не повезло.

Я хотел бы предложить дальнейшее чтение для всех, кто использует смесь Swift и ObjectiveC

Другие вопросы по тегам