首页 文章

协议扩展编译器错误中的Swift 2.2 #selector

提问于
浏览
26

我有一个协议扩展,它曾经在swift 2.2之前完美地工作 .

现在我有一个警告,告诉我使用新的 #selector ,但如果我添加它

没有使用Objective-C Selector声明的方法 .

我尝试在这几行代码中重现这个问题,可以很容易地复制并粘贴到游乐场

protocol Tappable {
    func addTapGestureRecognizer()
    func tapGestureDetected(gesture:UITapGestureRecognizer)
}

extension Tappable where Self: UIView {
    func addTapGestureRecognizer() {
        let gesture = UITapGestureRecognizer(target: self, action:#selector(Tappable.tapGestureDetected(_:)))
        addGestureRecognizer(gesture)
    }
}

class TapView: UIView, Tappable {
    func tapGestureDetected(gesture:UITapGestureRecognizer) {
        print("Tapped")
    }
}

还有一个建议是在协议 @objc 中附加到该方法,但如果我这样做也要求我将它添加到实现它的类中,但是一旦我添加了类,那么似乎没有看到协议中的实现延期 .
我该如何正确实现?

5 回答

  • 6

    我遇到了类似的问题 . 这就是我做的 .

    • 将协议标记为@objc .

    • 将我使用默认行为扩展的任何方法标记为可选 .

    • 然后使用Self . 在#selector中 .

    @objc public protocol UpdatableUserInterfaceType {
      optional func startUpdateUITimer()
      optional var updateInterval: NSTimeInterval { get }
      func updateUI(notif: NSTimer)
    }
    
    public extension UpdatableUserInterfaceType where Self: ViewController {
    
      var updateUITimer: NSTimer {
        return NSTimer.scheduledTimerWithTimeInterval(updateInterval, target: self, selector: #selector(Self.updateUI(_:)), userInfo: nil, repeats: true)
      }
    
      func startUpdateUITimer() {
        print(updateUITimer)
      }
    
      var updateInterval: NSTimeInterval {
        return 60.0
      }
    }
    
  • 0

    您可以创建一个属性作为选择器...示例:

    protocol Tappable {
        var selector: Selector { get }
        func addTapGestureRecognizer()
    }
    
    extension Tappable where Self: UIView {
        func addTapGestureRecognizer() {
            let gesture = UITapGestureRecognizer(target: self, action: selector)
            addGestureRecognizer(gesture)
        }
    }
    
    class TapView: UIView, Tappable {
        var selector = #selector(TapView.tapGestureDetected(_:))
    
        func tapGestureDetected(gesture:UITapGestureRecognizer) {
            print("Tapped")
        }
    }
    

    错误停止显示,并且不再需要使用@objc装饰器设置协议和类 .

    这个解决方案不是最优雅的,但直到现在看起来还不错 .

  • 24

    这个答案与Bruno Hecktheuers非常相似,但是我们选择将它作为参数传递给addTapGestureRecognizer函数,而不是让每个想要符合“Tappable”协议的人实现变量“selector” .

    protocol Tappable {
        func addTapGestureRecognizer(selector selector: Selector)
        func tapGestureDetected(gesture:UITapGestureRecognizer)
    }
    
    extension Tappable where Self: UIView {
        func addTapGestureRecognizer(selector selector: Selector)
            let gesture = UITapGestureRecognizer(target: self, action: selector)
            addGestureRecognizer(gesture)
        }
    }
    
    class TapView: UIView, Tappable {    
        func tapGestureDetected(gesture:UITapGestureRecognizer) {
            print("Tapped")
        }
    }
    

    然后只需将选择器传递到任何地方:

    addTapGestureRecognizer(selector: #selector(self.tapGestureDetected(_:)))
    

    这样我们就可以避免让实现此协议的人必须实现选择器变量,并且我们也避免使用“@objc”标记每个人使用此协议 . 像这种方法的感觉不那么臃肿 .

  • 16

    这是一个使用Swift 3的工作示例 . 它使用标准的Swift协议,无需任何 @objc 装饰和私有扩展来定义回调函数 .

    protocol PlayButtonPlayable {
    
        // be sure to call addPlayButtonRecognizer from viewDidLoad or later in the display cycle
        func addPlayButtonRecognizer()
        func handlePlayButton(_ sender: UITapGestureRecognizer)
    
    }
    
    fileprivate extension UIViewController {
        @objc func _handlePlayButton(_ sender: UITapGestureRecognizer) {
            if let playable = self as? PlayButtonPlayable {
                playable.handlePlayButton(sender)
            }
        }
    }
    
    fileprivate extension Selector {
        static let playTapped =
            #selector(UIViewController._handlePlayButton(_:))
    }
    
    extension PlayButtonPlayable where Self: UIViewController {
    
        func addPlayButtonRecognizer() {
            let playButtonRecognizer = UITapGestureRecognizer(target: self, action: .playTapped)
            playButtonRecognizer.allowedPressTypes = [ NSNumber(value: UIPressType.playPause.rawValue as Int) ]
            view.addGestureRecognizer(playButtonRecognizer)
        }
    
    }
    
  • 4

    我碰巧在侧栏看到这个,我最近遇到了同样的问题..不幸的是,由于Objective-C运行时限制你不能在协议扩展上使用@objc,我相信这个问题在今年年初就已经关闭了 .

    出现这个问题是因为在协议符合性之后添加了扩展,因此无法保证满足协议的一致性 . 也就是说,可以从NSObject子类和符合协议的任何东西中调用方法作为选择器 . 这通常是通过授权完成的 .

    这意味着您可以创建一个符合协议的空包装子类,并使用包装器从包装器中定义的协议调用其方法,协议中的任何其他未定义方法都可以传递给委托 . 还有其他类似的解决方案使用具体类的私有扩展,如UIViewController,并定义一个调用协议方法的方法,但这些方法也绑定到一个特定的类,而不是特定类的默认实现,恰好符合协议 .

    意识到您正在尝试实现协议函数的默认实现,该函数使用其自己的另一个协议函数来为其自己的实现定义值 . 噢!

    Protocol:

    public protocol CustomViewDelegate {
         func update()
         func nonDelegatedMethod()
    }
    

    View:

    使用委托,并定义一个包装器方法来安全地解包委托的方法 .

    class CustomView: UIView {
    
        let updateButton: UIButton = {
            let button = UIButton(frame: CGRect(origin: CGPoint(x: 50, y: 50), size: CGSize(width: 150, height: 50)))
            button.backgroundColor = UIColor.lightGray
            button.addTarget(self, action: #selector(doDelegateMethod), for: .touchUpInside)
            return button
        }()
    
        var delegate:CustomViewDelegate?
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("Pew pew, Aghh!")
        }
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            addSubview(updateButton)
        }
    
        @objc func doDelegateMethod() {
            if delegate != nil {
               delegate!.update()
            } else {
               print("Gottfried: I wanted to be a brain surgeon, but I had a bad habit of dropping things")
            }
         }
    
    
       }
    

    ViewController:

    使View Controller符合视图的委托:并实现协议的方法 .

    class ViewController: UIViewController, CustomViewDelegate {
    
        let customView = CustomView(frame: CGRect(origin: CGPoint(x: 100, y: 100), size: CGSize(width: 200, height: 200)))
    
        override func viewDidLoad() {
            super.viewDidLoad()
            customView.backgroundColor = UIColor.red
            customView.delegate = self //if delegate is not set, the app will not crash
            self.view.addSubview(customView)
        }
    
        // Protocol -> UIView Button Action -> View Controller's Method
        func update() {
            print("Delegating work from View that Conforms to CustomViewDelegate to View Controller")
        }
    
        //Protocol > View Controller's Required Implementation
        func nonDelegatedMethod() {
    
           //Do something else 
    
       }
    }
    

    请注意,视图控制器只需要符合委托,并且没有设置视图的某些属性的选择器,这将视图(和它的协议)与视图控制器分开 .

    您已经有一个名为TapView的UIView,它继承自UIView和Tappable,因此您的实现可能是:

    Protocol:

    protocol TappableViewDelegate {
        func tapGestureDetected(gesture:UITapGestureRecognizer)
    }
    

    TappableView:

    class TappableView: UIView {
    
        var delegate:TappableViewDelegate?
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("Pew pew, Aghh!")
        }
    
        override init(frame: CGRect) {
            super.init(frame: frame)
    
            let gesture = UITapGestureRecognizer(target: self, action: #selector(doDelegateMethod(gesture:)))
            addGestureRecognizer(gesture)
        }
    
        @objc func doDelegateMethod(gesture:UITapGestureRecognizer) {
            if delegate != nil {
                delegate!.tapGestureDetected(gesture: gesture)
            } else {
                print("Gottfried: I wanted to be a brain surgeon, but I had a bad habit of dropping things")
            }
        }
    
    }
    

    ViewController:

    class ViewController: UIViewController, TappableViewDelegate {
    
        let tapView = TappableView(frame: CGRect(origin: CGPoint(x: 100, y: 100), size: CGSize(width: 200, height: 200)))
    
        override func viewDidLoad() {
            super.viewDidLoad()
            tapView.backgroundColor = UIColor.red
            tapView.delegate = self
            self.view.addSubview(tapView)
        }
    
        func tapGestureDetected(gesture: UITapGestureRecognizer) {
            print("User did tap")
       }
    
    }
    

相关问题