使用选择器错误的Swift 3协议扩展

Aar*_*ron 21 protocols objective-c ios swift swift3

我有我认为是一个非常简单的协议扩展,我UIViewController提供了通过轻击手势解雇键盘的功能.这是我的代码:

@objc protocol KeyboardDismissing { 
    func on(tap: UITapGestureRecognizer)
}

extension KeyboardDismissing where Self: UIViewController {

    func addDismissalGesture() {
        let tap = UITapGestureRecognizer(target: self, action: #selector(Self.on(tap:)))
        view.addGestureRecognizer(tap)
    }

    func on(tap: UITapGestureRecognizer) {
        dismissKeyboard()
    }

    func dismissKeyboard() {
        view.endEditing(true)
    }
}
Run Code Online (Sandbox Code Playgroud)

问题是上面的代码在这一行引发编译错误:

let tap = UITapGestureRecognizer(target: self, action: #selector(Self.on(tap:)))
Run Code Online (Sandbox Code Playgroud)

带有错误消息:

'#selector'的参数是指没有暴露给Objective-C的实例方法'on(tap :)'

建议通过@objc之前添加"修复它"func on(tap: UITapGestureRecognizer)

好的,我添加标签:

@objc func on(tap: UITapGestureRecognizer) {
    dismissKeyboard()
}
Run Code Online (Sandbox Code Playgroud)

但是,它会在这个新添加的@objc标记上抛出一个不同的编译错误,并显示错误消息:

@objc只能用于类的成员,@ objc协议和类的具体扩展

通过删除我刚刚被告知添加的完全相同的标签来建议"修复它" .

我原先认为@objc在我的协议定义之前添加可以解决任何#selector问题,但显然情况并非如此,这些周期性错误消息/建议没有丝毫帮助.我已经@objc在各处添加/删除标签optional,将方法标记为,将方法放在协议的定义中等等.

我在协议定义中放置的内容也无关紧要使扩展名保持不变,以下示例不起作用,也不对协议定义中声明的方法进行任何组合:

@objc protocol KeyboardDismissing { 
    func on(tap: UITapGestureRecognizer)
}
Run Code Online (Sandbox Code Playgroud)

这让我觉得它可以通过编译作为一个独立的协议来工作,但第二个我尝试将它添加到一个视图控制器:

class ViewController: UIViewController, KeyboardDismissing {}
Run Code Online (Sandbox Code Playgroud)

它吐回原来的错误.

有人可以解释我做错了什么以及如何编译它?

注意:

我已经看过这个问题,但它是针对Swift 2.2而不是Swift 3,一旦创建了一个继承自示例中定义的协议的视图控制器类,答案就会编译.

我也看了这个问题,但答案使用NotificationCenter的不是我所追求的.

如果还有其他看似重复的问题,请告诉我.

Fré*_*dda 40

马特的回答是正确的.但是,我只想补充一点,如果您正在处理#selector以使用NotificationCenter通知,您可以尝试通过使用闭包版本来避免#selector.

例:

而不是写:

extension KeyboardHandler where Self: UIViewController {

    func startObservingKeyboardChanges() {

        NotificationCenter.default.addObserver(
            self,
            selector: #selector(keyboardWillShow(_:)),
            // !!!!!            
            // compile error: cannot be included in a Swift protocol
            name: .UIKeyboardWillShow,
            object: nil
        )
    }

     func keyboardWillShow(_ notification: Notification) {
       // do stuff
    }
}
Run Code Online (Sandbox Code Playgroud)

你可以写:

extension KeyboardHandler where Self: UIViewController {

    func startObservingKeyboardChanges() {

        // NotificationCenter observers
        NotificationCenter.default.addObserver(forName: .UIKeyboardWillShow, object: nil, queue: nil) { [weak self] notification in
            self?.keyboardWillShow(notification)
        }
    }

    func keyboardWillShow(_ notification: Notification) {
       // do stuff
    }
}
Run Code Online (Sandbox Code Playgroud)


mat*_*att 14

这是一个Swift协议扩展.Swift协议扩展对于Objective-C是不可见的,无论如何; 它对它们一无所知.但是#selector关于Objective-C看到并调用你的函数.这不会发生,因为您的on(tap:)函数在协议扩展中定义.因此编译器正确地阻止了你.

这个问题是一大类问题之一,人们认为他们通过协议扩展在处理Cocoa时会变得聪明,试图通过协议将Objective-C可调用功能(选择器,委托方法等)注入类中延期.这是一个吸引人的概念,但它不会起作用.