【问题标题】:Swift 2.2 #selector in protocol extension compiler errorSwift 2.2 #selector 中的协议扩展编译器错误
【发布时间】:2016-07-11 03:24:19
【问题描述】:

我有一个协议扩展,它在 swift 2.2 之前可以完美运行。

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

没有使用 Objective-C 选择器声明的方法。

我尝试在这几行代码中重现该问题,可以轻松复制并粘贴到操场中

  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 中附加到该方法,但如果我这样做,我也会将它添加到实现它的类中,但是一旦我添加了该类不符合协议不再,因为它似乎没有在协议扩展中看到实现。
我怎样才能正确地实现它?

【问题讨论】:

  • 你能发布一个可编译的例子吗? panGestureDetected 是如何声明的?
  • 明天补上,谢谢Sulthan
  • @Andrea Change Pannable.panGestureDetected(_:) 。放置声明 panGestureDetected 的类的名称而不是 Pannable.whatever 只需放置 YourClass.panGestureDetected(_:)
  • Leo Dabus:这样做意味着我需要为我拥有的每个 tapGestureDetected 实现创建一个协议扩展。 @Sulthan 我更新了完整代码,或者可以在这里下载 dropbox.com/s/0bn1u9ndnzk1c2e/Selector.playground.zip?dl=0

标签: ios swift protocols


【解决方案1】:

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

  1. 将协议标记为@objc。
  2. 将我使用默认行为扩展的所有方法标记为可选。
  3. 然后使用自我。在#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
      }
    }
    

【讨论】:

  • @Andrea,酷!很高兴我能帮上忙。
  • 我收到“使用未解析的标识符‘Self’。我的协议未标记为公共。
  • 呃,当我们有关联类型要求时不起作用
  • 这仍然有警告信使:非@objc方法不满足'@objc'协议的可选要求
【解决方案2】:

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

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 装饰器设置协议和类。

这个解决方案不是最优雅的,但到目前为止看起来还可以。

【讨论】:

  • 是的,不是那么棒,但比将@objc 放在任何地方要好得多,特别是如果您从具有默认实现的其他协议链“继承”。突然之间,你最终改变了你的整个代码库,只是因为那个选择器。
  • 如何调用协议自己的函数作为选择器,我不想在类扩展协议中声明选择器...
【解决方案3】:

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

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”标记使用这个协议的每个人。感觉这种做法不那么臃肿了。

【讨论】:

  • 如果你想在协议扩展本身而不是在实现协议的具体类中提供tapGestureDetected的默认实现怎么办?
【解决方案4】:

这是一个使用 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)
    }

}

【讨论】:

    【解决方案5】:

    我碰巧在侧边栏中看到了这个,我最近也遇到了同样的问题。不幸的是,由于 Objective-C 运行时的限制,你不能在协议扩展上使用@objc,我相信这个问题在今年早些时候已经解决了。

    问题出现是因为扩展是在协议一致性之后添加的,因此无法保证满足协议一致性。也就是说,有可能从任何继承 NSObject 并符合协议的对象中调用方法作为选择器。这通常通过委托来完成。

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

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

    协议:

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

    查看:

    使用委托,并定义包装方法以安全地解开委托的方法。

    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")
            }
         }
    
    
       }
    

    视图控制器:

    使视图控制器符合视图的委托:并实现协议的方法。

    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 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")
            }
        }
    
    }
    

    视图控制器:

    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")
       }
    
    }
    

    【讨论】:

      猜你喜欢
      • 2015-10-13
      • 1970-01-01
      • 2017-03-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-04
      相关资源
      最近更新 更多