Расширение протокола по протоколу ObjC
У меня есть протокол Objective C, который используется в основном объектами C и одним или двумя объектами Swift.
Я хотел бы расширить протокол в Swift и добавить 2 функции. Один для регистрации уведомления, а другой для обработки уведомления.
Если я добавлю эти
func registerForPresetLoadedNotification() {
NSNotificationCenter.defaultCenter().addObserver(self as AnyObject,
selector: #selector(presetLoaded(_:)),
name: kPresetLoadedNotificationName,
object: nil)
}
func presetLoaded(notification: NSNotification) {
}
Я получаю ошибку на #selector, которая говорит 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
Я также не могу пометить расширение протокола как @objc
Когда я создаю протокол Objective C в качестве протокола Swift, я получаю ту же ошибку.
Есть ли способ добиться этого, который будет работать для классов Objective-C и Swift, которые используют протокол?
2 ответа
Действительно, вы не можете пометить функцию расширения протокола как @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
Я нашел способ сделать это:) Просто избегайте @objc всего вместе:D
//Adjusts UITableView content height when keyboard show/hide
public protocol KeyboardObservable: NSObjectProtocol {
func registerForKeyboardEvents()
func unregisterForKeyboardEvents()
}
extension KeyboardObservable where Self: UITableView {
public func registerForKeyboardEvents() {
let defaultCenter = NotificationCenter.default
var tokenShow: NSObjectProtocol!
tokenShow = defaultCenter.addObserver(forName: .UIKeyboardDidShow, object: nil, queue: nil) { [weak self] (notification) in
guard self != nil else {
defaultCenter.removeObserver(tokenShow)
return
}
self!.keyboardWilShow(notification as NSNotification)
}
var tokenHide: NSObjectProtocol!
tokenHide = defaultCenter.addObserver(forName: .UIKeyboardWillHide, object: nil, queue: nil) { [weak self] (notification) in
guard self != nil else {
defaultCenter.removeObserver(tokenHide)
return
}
self!.keyboardWilHide(notification as NSNotification)
}
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")
}
}