2016-08-16 172 views
6

我有一个Objective-C协议,主要用于Objective-C对象和一个或两个Swift对象。在ObjC协议上的协议扩展

我想在Swift中扩展协议并添加2个函数。一个注册通知,另一个处理通知。

如果我添加这些

func registerForPresetLoadedNotification() { 
    NSNotificationCenter.defaultCenter().addObserver(self as AnyObject, 
                selector: #selector(presetLoaded(_:)), 
                name: kPresetLoadedNotificationName, 
                object: nil) 
} 

func presetLoaded(notification: NSNotification) { 

} 

我得到它说Argument of '#selector' refers to a method that is not exposed to Objective-C

如果我然后标记presetLoaded为@objc我得到它说@objc can only be used with members of classes, @objc protocols, and concrete extensions of classes

我也是一个错误的#selector错误不能将协议扩展标记为@objc

当我创建Objective-C协议作为Swift协议我得到同样的错误。

有没有一种方法可以实现这一点,将使用协议的Objective-C和Swift类?

+0

如何扩展协议并将这些函数定义为_optional_ functions在Objective-C中并在Swift中实现它们?只是一个想法。 – skim

+0

我的想法是在扩展中实现注册函数,然后覆盖objective-c和swift类中的通知处理函数。我需要在objective-c类中设置参数,在Swift中实现它们不会工作。 – jarryd

+0

我不确定我是否正确理解你。这听起来像你想要做的是扩展(添加功能到现有的协议),并在ObjC中提供一个默认实现,并在Swift中重写该实现的一部分。它是否正确? – skim

回答

3

事实上,你不能真正标记协议扩展的函数,如@objc(或动态,其是由方式当量)。 Objective-C运行时只允许调用类的方法。如果你真的想通过协议扩展,我可以提出以下解决方案(假设你的原始协议被命名为ObjcProtocol)。

让我们为我们的通知处理器的包装:

final class InternalNotificationHandler { 
    private let source: ObjcProtocol 

    init(source: ObjcProtocol) { 
     // We require source object in case we need access some properties etc. 
     self.source = source 
    } 

    @objc func presetLoaded(notification: NSNotification) { 
     // Your notification logic here 
    } 
} 

现在,我们需要扩大我们的ObjcProtocol引进所需的逻辑

import Foundation 
import ObjectiveC 

internal var NotificationAssociatedObjectHandle: UInt8 = 0 

extension ObjcProtocol { 
    // This stored variable represent a "singleton" concept 
    // But since protocol extension can only have stored properties we save it via Objective-C runtime 
    private var notificationHandler: InternalNotificationHandler { 
     // Try to an get associated instance of our handler 
     guard let associatedObj = objc_getAssociatedObject(self, &NotificationAssociatedObjectHandle) 
      as? InternalNotificationHandler else { 
      // If we do not have any associated create and store it 
      let newAssociatedObj = InternalNotificationHandler(source: self) 
      objc_setAssociatedObject(self, 
            &NotificationAssociatedObjectHandle, 
            newAssociatedObj, 
            objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 
      return newAssociatedObj 
     } 

     return associatedObj 
    } 

    func registerForPresetLoadedNotification() { 
     NSNotificationCenter.defaultCenter().addObserver(self, 
                 selector: #selector(notificationHandler.presetLoaded(_:)), 
                 name: kPresetLoadedNotificationName, 
                 object: self) 
    } 

    func unregisterForPresetLoadedNotification() { 
     // Clear notification observer and associated objects 
     NSNotificationCenter.defaultCenter().removeObserver(self, 
                  name: kPresetLoadedNotificationName, 
                  object: self) 
     objc_removeAssociatedObjects(self) 
    } 
} 

我知道这可能看起来不那么优雅,所以我我真的会考虑改变核心方法。

一个注意:你这样做可能要限制你的协议扩展

extension ObjcProtocol where Self: SomeProtocolOrClass 
0

我发现了一个办法做到这一点:)只要避免@objc一起:d

//Adjusts UITableView content height when keyboard show/hide 
public protocol KeyboardObservable { 
    func registerForKeyboardEvents() 
    func unregisterForKeyboardEvents() 
} 

extension KeyboardObservable where Self: UITableView { 

    public func registerForKeyboardEvents() { 
     NotificationCenter.default.addObserver(forName: .UIKeyboardDidShow, object: nil, queue: nil) { notification in 
      self.keyboardDidShow(notification) 
     } 
     NotificationCenter.default.addObserver(forName: .UIKeyboardWillHide, object: nil, queue: nil) { notification in 
      self.keyboardWillHide(notification) 
     } 
    } 

    private func keyboardDidShow(_ notification: Notification) { 
     let rect = ((notification as NSNotification).userInfo![UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue 
     let height = rect.height 
     var insets = UIEdgeInsetsMake(0, 0, height, 0) 
     insets.top = contentInset.top 
     contentInset = insets 
     scrollIndicatorInsets = insets 
    } 

    private func keyboardWillHide(_ notification: Notification) { 
     var insets = UIEdgeInsetsMake(0, 0, 0, 0) 
     insets.top = contentInset.top 
     UIView.animate(withDuration: 0.3) { 
      self.contentInset = insets 
      self.scrollIndicatorInsets = insets 
     } 
    } 

    public func unregisterForKeyboardEvents() { 
     NotificationCenter.default.removeObserver(self) 
    } 

} 

class CreateStudentTableView: UITableView, KeyboardObservable { 

    init(frame: CGRect, style: UITableViewStyle) { 
    super.init(frame: frame, style: style) 
    registerForKeyboardEvents() 
    } 

    required init?(coder aDecoder: NSCoder) { 
    fatalError("init(coder:) has not been implemented") 
    } 

    deinit { 
    unregisterForKeyboardEvents() 
    } 
}