如何为Swift协议提供异步方法与完成回调二选一的实现选项?
方案可行性分析与实现建议
你的初始方案无法实现“二选一且必须实现其一”的需求——当前结构下,Foo协议强制要求实现带completion的render方法,扩展提供的异步render是基于该回调方法的默认实现,开发者只能先实现回调版,无法选择只实现异步版。
要实现“二选一”的强制要求,可采用以下两种编译时安全的方案:
方案一:协议双方法+互依赖默认实现
将两个render方法都声明在协议中,通过扩展为彼此提供默认实现,让开发者只需实现其中一个即可满足协议要求:
public protocol Foo { func render(completion: (Bool) -> Void) func render() async -> Bool } // 实现异步版时,自动生成回调版的默认实现 public extension Foo { func render(completion: (Bool) -> Void) { Task { let result = await render() completion(result) } } } // 实现回调版时,自动生成异步版的默认实现 public extension Foo { func render() async -> Bool { return await withCheckedContinuation { continuation in render(completion: { result in continuation.resume(returning: result) }) } } }
注意事项
- 编译器会强制开发者至少实现一个方法(若两个都不实现,调用时会触发无限递归,因此需在框架文档中明确要求必须实现其一);
- 该方案无需额外类型转换,框架内部可直接调用任意一个
render方法。
方案二:拆分协议+编译时约束
将回调版和异步版拆分为两个子协议,主协议继承两者,通过类型约束确保开发者必须实现其中一个:
// 拆分两个子协议分别定义不同的render接口 public protocol FooCallbackRender { func render(completion: (Bool) -> Void) } public protocol FooAsyncRender { func render() async -> Bool } // 主协议,要求遵循至少一个子协议 public protocol Foo: FooCallbackRender & FooAsyncRender {} // 为实现回调版的类型提供异步接口默认实现 public extension Foo where Self: FooCallbackRender { func render() async -> Bool { await withCheckedContinuation { cont in render(completion: cont.resume(returning:)) } } } // 为实现异步版的类型提供回调接口默认实现 public extension Foo where Self: FooAsyncRender { func render(completion: (Bool) -> Void) { Task { completion(await render()) } } }
框架初始化与调用优化
为避免开发者传入空实现的Foo类型,可在初始化时增加运行时检查:
public class MyFramework { private let delegate: Foo public init(delegate: Foo) { guard delegate is FooCallbackRender || delegate is FooAsyncRender else { fatalError("Delegate must implement either render(completion:) or render() async") } self.delegate = delegate } public func performRender() async { // 框架内部可直接调用任意版本的render方法 let result = await delegate.render() // 处理渲染结果 } }
优势
- 编译时强制开发者实现对应子协议的方法,避免空实现;
- 框架内部调用无需区分类型,直接使用统一接口。
内容的提问来源于stack exchange,提问作者Tometoyou
相关产品推荐
相关产品推荐

