You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 07:35:22