В более крупной базе кода, смешанной с ObjC/Swift, у меня есть протокол ObjC MyObjCProtocol <NSObject>, которому должны соответствовать другие классы ObjC. Он объявляет строковое свойство @property (copy) NSString *myString.
Теперь у одного из моих объектов Swift есть свойство conformsToObjCProtocol, объявленное в соответствии с этим протоколом:
let conformsToObjCProtocol: MyObjCProtocol
Все это работает хорошо.
Но когда я пытаюсь наблюдать myString с помощью Комбайна вот так:
let cancellable = conformsToObjCProtocol.publisher(for: \.myString).sink { print($0) }
Я получаю сообщение об ошибке Value of type 'any MyObjCProtocol' has no member 'publisher'.
Я считаю, что это должно работать, потому что MyObjCProtocol объявлен соответствующим NSObject. Я также добавил MyObjCProtocol.h в заголовок моста, так что это не так. Я не могу переписать протокол в Swift, потому что мне нужны классы ObjC, чтобы ему соответствовать. Что я могу с этим поделать?





Проблема, с которой вы столкнулись, связана с тем, что, хотя ваш протокол MyObjCProtocol наследует от NSObject, Объединение не распознает автоматически свойства для публикации. Метод publisher(for:) — это расширение, предоставляемое Joint для объектов, поддерживающих наблюдение за ключом и значением (KVO), которое не применяется напрямую к протоколам в Objective-C.
Вот решение в Swift для решения этой проблемы:
Создайте расширение для NSObject. Вы можете создать расширение для NSObject, которое предоставляет метод издателя для свойств, совместимых с KVO.
Используйте AnyObject вместо MyObjCProtocol: поскольку для объединения требуется конкретная ссылка, а ваш протокол — это всего лишь абстракция, вам нужно использовать AnyObject и убедиться, что ваш конкретный объект соответствует протоколу.
Вот пример того, как вы можете это сделать:
import Combine
import Foundation
extension NSObject {
func publisher<T>(for keyPath: KeyPath<NSObject, T>) -> AnyPublisher<T, Never> {
Publishers.KVObservablePublisher(object: self, keyPath: keyPath)
.eraseToAnyPublisher()
}
}
import Combine
// Ensure your concrete object is an NSObject and conforms to the protocol
let objcObject: AnyObject = conformsToObjCProtocol as AnyObject
// Force cast objcObject to NSObject to use the extension
guard let nsObject = objcObject as? NSObject else {
fatalError("The object is not an NSObject")
}
// Create the publisher and subscribe to it
let cancellable = nsObject.publisher(for: \.myString).sink { newValue in
print(newValue)
}
Убедитесь, что myString правильно отображается для KVO в вашем классе Objective-C. Это должно решить проблему и позволить вам наблюдать за изменениями в свойстве с помощью объединения.
Протокол Objective-C импортируется в Swift как протокол, требующий соответствия NSObjectProtocol. Для этого не требуется, чтобы конформер наследовал NSObject.
Если вы объявите свойство следующим образом:
let conformsToObjCProtocol: MyObjCProtocol & NSObject
то это можно наблюдать с помощью NSObject.KeyValueObservingPublisher.
NSObject.KeyValueObservingPublisher(
object: conformsToObjCProtocol, keyPath: \.myString, options: []
).sink { ... }
К сожалению, метод publisher здесь нельзя использовать из-за того, как он объявлен. Объявление требует, чтобы вы вызывали его для конкретного типа.
Спасибо за отличный подробный ответ! Вы уверены насчет «KVObservablePublisher»? Компилятор жалуется на это, и я нигде не могу найти символ с таким именем...