Swift协议扩展isEqual未调用,如何封装Struct供ObjC调用并消除冗余?
问题描述
我想把Swift Struct封装成包装类,避免直接将Struct转为@objc类污染Swift API。由于@objc类不支持泛型,我尝试通过继承NSObject的InitializableConverter协议消除冗余代码——比如实现基于底层Struct的isEqual相等性判断方法,但协议扩展中的该方法始终未被调用;同时我希望将初始化器移至协议扩展的默认实现,目前也未能实现,求可行的解决方案。
相关代码
public protocol InitializableConverter: NSObject { associatedtype SwiftStructType: Equatable var _data: SwiftStructType { get set } init(_ data: SwiftStructType) } extension InitializableConverter { /// 基于底层Struct判断对象相等性 func isEqual(_ object: Any?) -> Bool { if let other = object as? Self { return self._data == other._data } return false } } @objc public class UserSettingsObjc: NSObject, InitializableConverter { public var _data: UserSettings required public init(_ data: UserSettings) { _data = data } @objc public var locale: String { _data.locale } } public struct UserSettings: Equatable { public private(set) var locale: String public init(locale: String) { self.locale = locale } }
补充使用场景代码
// 原Swift方法,使用Result<>语法 func call(username: String?, password: String?, completion: @escaping (Result<UserSettings, MyError>) -> Void) { completion(.success(UserSettings(locale: "hello world"))) } // Objective-C适配方法,使用非Result<>语法(@objc在Playground中被注释) @objc func wrappedCall(username: String?, password: String?, completion: @escaping (UserSettingsObjc?, MyErrorObjc?) -> Void) { call(username: username, password: password) { result in callCompletion(completion, with: result) } } // 通过InitializableConverter协议将Result转为非Result格式 func callCompletion<T, TOrig, E, Eorig>( _ completion: (T?, E?) -> Void, with result: Result<TOrig, Eorig> ) where T: InitializableConverter, E: InitializableConverter { switch result { case let .success(torig): return completion(T(torig as! T.SwiftStructType), nil) case let .failure(eorig): return completion(nil, E(eorig as! E.SwiftStructType)) } }
解决方案
1. 修复isEqual方法未被调用的问题
NSObject的isEqual(_:)依赖Objective-C动态消息派发,Swift协议扩展的默认实现无法被该机制识别。解决方式是让协议显式声明该方法,并在遵守协议的类中显式调用扩展逻辑:
public protocol InitializableConverter: NSObject { associatedtype SwiftStructType: Equatable var _data: SwiftStructType { get set } init(_ data: SwiftStructType) // 显式声明需重写NSObject的isEqual方法 override func isEqual(_ object: Any?) -> Bool } extension InitializableConverter { func isEqual(_ object: Any?) -> Bool { guard let other = object as? Self else { return false } return self._data == other._data } } // 遵守协议的类中显式调用扩展实现 @objc public class UserSettingsObjc: NSObject, InitializableConverter { public var _data: UserSettings required public init(_ data: UserSettings) { _data = data } @objc public var locale: String { _data.locale } override public func isEqual(_ object: Any?) -> Bool { return (self as InitializableConverter).isEqual(object) } }
2. 将初始化器移至协议扩展的默认实现
由于协议关联了NSObject,扩展中的初始化器必须先调用NSObject的指定初始化器init(),再完成Struct的赋值。遵守协议的类只需声明required init即可复用扩展逻辑:
public protocol InitializableConverter: NSObject { associatedtype SwiftStructType: Equatable var _data: SwiftStructType { get set } init(_ data: SwiftStructType) } extension InitializableConverter { // 协议扩展提供初始化器默认实现 init(_ data: SwiftStructType) { self.init() // 先完成NSObject初始化 self._data = data } func isEqual(_ object: Any?) -> Bool { guard let other = object as? Self else { return false } return self._data == other._data } } // 简化后的包装类 @objc public class UserSettingsObjc: NSObject, InitializableConverter { public var _data: UserSettings // 仅需声明required init,复用扩展逻辑 required public init(_ data: UserSettings) { super.init() self._data = data } @objc public var locale: String { _data.locale } override public func isEqual(_ object: Any?) -> Bool { return (self as InitializableConverter).isEqual(object) } }
3. 优化Result转换方法的类型安全性
原方法中的as!强制转换存在崩溃风险,可通过协议关联类型直接约束类型,消除强制转换:
func callCompletion<T, E>( _ completion: (T?, E?) -> Void, with result: Result<T.SwiftStructType, E.SwiftStructType> ) where T: InitializableConverter, E: InitializableConverter { switch result { case let .success(data): completion(T(data), nil) case let .failure(error): completion(nil, E(error)) } }
内容的提问来源于stack exchange,提问作者Shawn
相关产品推荐
相关产品推荐

