Protocol Extension with ObjC Protocol

I have an Objective-C protocol that is used mainly by Objective-C objects and one or two Swift objects.

I would like to extend the protocol in Swift and add 2 functions. One for registering for a notification and one for processing a notification.

If I add these

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

I get an error on #selector that says Argument of '#selector' refers to a method that is not exposed to Objective-C

If I then mark presetLoaded as @objc , I get the error message @objc can only be used with members of classes, @objc protocols, and concrete extensions of classes

I also cannot mark the protocol extension as @objc

When I create the Objective-C protocol as a Swift protocol, I get the same error.

Is there a way to achieve this that will work for Objective-C and Swift classes that use the protocol?

+6
source share
2 answers

Indeed, you cannot mark the protocol extension function as @objc (or dynamic ), which is equivalent, by the way). You can only send class methods using Objective-C runtime.

In your specific case, if you really want to do this through a protocol extension, I can offer the following solution (assuming your original protocol is named ObjcProtocol ).

Create a wrapper for our notification handler:

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

Now we need to expand our ObjcProtocol to introduce the required logic.

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

I know this may not seem very elegant, so I would think about changing the main approach.

Note: You can restrict the extension of the protocol.

 extension ObjcProtocol where Self: SomeProtocolOrClass 
+3
source

I found a way to do this :) Just avoid @objc all together: 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) } } 

Example

 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() } } 
0
source

All Articles