Combine使用assign(to:)绑定@Published属性报错原因咨询
问题背景
项目中先定义了用于表示远程请求状态的泛型枚举:
enum Remote<Content> { case .notAsked case .loading case .loaded(Content) case .failed(Error) }
后续实现遵循ObservableObject协议的视图模型时,初始写法触发编译错误:
class MyViewModel: ObservableObject { @Published var content: Remote<ContentStruct> = .notAsked func fetchContent() { content = .loading service.fetchContent() .receive(on: queue) .map(Remote<ContentStruct>.loaded) .catch { error in Just(.failed(error)) } .assign(to: &self.content) } }
对应编译错误信息:
Cannot convert value of type 'Remote<ContentStruct>' to expected argument type 'Published<Remote<ContentStruct>>.Publisher'
将代码改为KeyPath版本的assign写法后可正常运行:
func fetchContent() { content = .loading service.fetchContent() .receive(on: queue) .map(Remote<ContentStruct>.loaded) .catch { error in Just(.failed(error)) } .assign(to: \.content, on: self) .store(in: &cancellables) }
核心疑问点:
- 为什么传入inout引用的
assign(to:)方法无法完成绑定? - 该API是否需要额外适配?
- 此前了解到
assign相关API存在内存泄漏风险,升级到iOS14后该API的正确使用逻辑是什么?
原因说明
两个assign重载的核心差异
Combine框架提供了两个完全独立的assign订阅方法,行为差异极大:
- iOS13 提供的
assign(to:on:):接收引用可写KeyPath与目标对象,返回AnyCancellable实例。该API会强持有传入的目标对象,若将返回的cancellable存入目标对象自身的cancellables集合,会形成self → cancellables集合 → cancellable实例 → self的循环引用,这就是此前该API被诟病内存泄漏的根本原因。修改后的KeyPath写法虽然能通过编译,但依然存在该循环引用风险。 - iOS14 新增的
assign(to:)(接收inout参数的版本):专门为绑定@Published属性设计,不会强持有目标对象,订阅生命周期与目标@Published属性绑定,属性所属实例销毁时订阅会自动取消,无需手动持有cancellable,从设计上避免了循环引用问题。
编译错误的根因
初始写法报错的核心原因是传参类型不匹配:
iOS14新增的inout版本assign要求入参类型为inout Published<Output>.Publisher,即@Published属性的投影发布者的inout引用。
对于@Published修饰的属性,Swift提供了两种访问方式:
- 直接通过属性名访问(如
self.content):拿到的是属性存储的实际值,类型为Remote<ContentStruct> - 通过
$+属性名访问(如$content):拿到的是@Published对外暴露的发布者实例,类型为Published<Remote<ContentStruct>>.Publisher,完全匹配API的入参要求。
初始写法传入&self.content,本质是传入了实际值的inout引用,和API要求的发布者类型完全不匹配,因此触发类型转换错误。
正确使用方式
无需退回有泄漏风险的KeyPath写法,仅需修正入参,将&self.content替换为&$content即可,不需要额外将订阅存入cancellables集合:
func fetchContent() { content = .loading service.fetchContent() .receive(on: queue) .map(Remote<ContentStruct>.loaded) .catch { error in Just(.failed(error)) } .assign(to: &$content) }
注意:该inout版本的
assign仅支持绑定到@Published属性的发布者,无法用于普通类属性,这是它能避免循环引用的核心设计约束。
内容的提问来源于stack exchange,提问作者Fogmeister
相关产品推荐
相关产品推荐

