Swift中避免循环引用同时支持结构体遵循协议的方案问询
这个问题确实是Swift里值类型和引用类型混合使用协议时的典型痛点——既要让结构体能遵循协议,又要避免类和Foo之间的循环引用,咱们可以从几个角度来解决:
方案一:拆分协议,分别处理值类型和引用类型
核心思路是把原有的P协议拆成基础协议和类绑定子协议,让Foo内部用不同的存储属性分别处理值类型和引用类型的实例:
// 基础非类绑定协议,允许结构体遵循 protocol P { } // 类绑定的子协议,仅用于引用类型 protocol ClassP: P, AnyObject { } struct S: P { var foo: Foo init(withFoo foo: Foo) { self.foo = foo foo.assignValueP(self) } } class C: ClassP { var foo: Foo init(withFoo foo: Foo) { self.foo = foo foo.assignClassP(self) } } class Foo { // 存储值类型的P实例,强引用没问题(值拷贝不会循环) private var _valueP: P? // 存储引用类型的P实例,用weak避免循环引用 weak private var _classP: ClassP? // 对外统一提供P实例的访问入口 var p: P? { get { return _classP ?? _valueP } } // 专门给值类型调用的赋值方法 func assignValueP(_ p: P) { _valueP = p _classP = nil } // 专门给引用类型调用的赋值方法 func assignClassP(_ p: ClassP) { _classP = p _valueP = nil } }
这样一来,结构体S可以正常遵循P,类C遵循类绑定的ClassP,Foo对C用弱引用避免循环,对S用强引用也不会有内存问题,完美兼顾两种场景。
方案二:用类型擦除包装器统一处理
如果不想拆分协议,可以创建一个类型擦除的包装类AnyP,内部自动区分值类型和引用类型的存储方式:
protocol P { } struct S: P { var foo: Foo init(withFoo foo: Foo) { self.foo = foo foo.p = AnyP(self) } } class C: P { var foo: Foo init(withFoo foo: Foo) { self.foo = foo foo.p = AnyP(self) } } // 类型擦除的包装类,统一对外暴露P接口 class AnyP: P { private let _box: AnyPBox init(_ p: P) { // 判断实例是类类型还是值类型,选择不同的存储box if let classInstance = p as? AnyObject { _box = ClassBox(value: classInstance as! P & AnyObject) } else { _box = ValueBox(value: p) } } // 如果P有方法,在这里需要转发给内部的box,示例中P是空协议所以省略 } // 内部协议,用于统一不同box的类型 private protocol AnyPBox { } // 存储引用类型的box,用weak持有避免循环 private class ClassBox<T: P & AnyObject>: AnyPBox { weak var value: T? init(value: T) { self.value = value } } // 存储值类型的box,直接持有值 private struct ValueBox<T: P>: AnyPBox { var value: T } class Foo { var p: AnyP? = nil }
这个方案的好处是对外不需要修改原有的协议结构,所有区分逻辑都封装在AnyP内部,调用方的代码几乎不需要改动。
方案三:调整依赖关系,用回调替代直接持有
如果业务场景允许,咱们可以换个思路:不让Foo直接持有P的实例,而是用闭包回调的方式实现交互,从根源上避免循环引用:
protocol P { } struct S: P { var foo: Foo init(withFoo foo: Foo) { self.foo = foo // 给Foo设置回调闭包,值类型捕获自身的拷贝,不会有循环问题 foo.interactionCallback = { // 处理Foo的通知逻辑 print("S received callback from Foo") } } } class C: P { var foo: Foo init(withFoo foo: Foo) { self.foo = foo // 引用类型用weak self捕获,避免循环引用 foo.interactionCallback = { [weak self] in guard let self = self else { return } print("C received callback from Foo") } } } class Foo { // 用闭包替代直接持有P实例 var interactionCallback: (() -> Void)? = nil // 示例:触发回调的方法 func triggerInteraction() { interactionCallback?() } }
这种方式彻底改变了依赖方向,Foo不再持有任何P类型的引用,而是通过闭包通知对方,不管是值类型还是引用类型都能完美适配,代码也更简洁。
内容的提问来源于stack exchange,提问作者Grant Palmer
相关产品推荐
相关产品推荐

