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

iOS14中传入的Published属性无法触发onReceive,但iOS16正常

问题描述

我实现了一个接收Publisher的ViewModifier,代码如下:

struct MyViewModifier: ViewModifier {
  let publisher: Published<Bool>.Publisher
  
  func body(content: Content) -> some View {
    content
      .onReceive(publisher) { value in
        print("received") // 从未执行
      }
  }
}

extension View {
  func listen(
    to publisher: Published<Bool>.Publisher
  ) -> some View {
    ModifiedContent(
      content: self,
      modifier: MyViewModifier(
        publisher: publisher
      )
    )
  }
}

将其附加到LoadingView上,监听MyModel中@Published修饰的publisher属性,LoadingView代码:

import SwiftUI

struct LoadingView: View {
  @ObservedObject var model: MyModel

  var body: some View {
    Text("test")
        .listen(
          to: model.$publisher
        )
    }
  }
}

MyModel是ObservableObject,publisher为@Published属性。通过MyViewController创建并传入model,代码:

final class MyViewController: UIHostingController<LoadingView> {
  let model: MyModel

  init() {
    model = LoadingModel()
    super.init(rootView: LoadingView(model: model))
    view.backgroundColor = .clear
    view.translatesAutoresizingMaskIntoConstraints = false
  }

  @MainActor required dynamic init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  func togglePublisher(on: Bool) {
    model.publisher = on
  }
}

在iOS16中修改model.publisher值时,onReceive能正常响应,但在iOS14中print("received")从未执行,请问原因是什么?

原因分析

这是iOS14中SwiftUI对@Published publisher的处理机制缺陷导致的:

  • Published<T>.Publisher是值类型,在iOS14中,每次访问model.$publisher都会生成一个新的Publisher实例;
  • 当View的body重新计算时(比如ObservedObject的任意属性变化),会重新创建MyViewModifier实例并传入新的Publisher,之前的订阅会被销毁;
  • 更关键的是,iOS14中单独传递的Published.Publisher无法正确关联到ObservableObject的生命周期,导致属性变化时,publisher无法将事件传递到onReceive的回调中。

iOS16中SwiftUI修复了这个问题,优化了@Published publisher的传递逻辑,确保即使作为值类型传递,也能维持与ObservableObject的绑定关系,正确触发订阅回调。

解决方案

可以采用以下几种方式解决iOS14的兼容性问题:

方案一:改用Binding结合onChange监听

修改ViewModifier,通过Binding<Bool>接收值,使用onChange监听变化:

struct MyViewModifier: ViewModifier {
  let binding: Binding<Bool>
  
  func body(content: Content) -> some View {
    content
      .onChange(of: binding.wrappedValue) { value in
        print("received")
      }
  }
}

extension View {
  func listen(to binding: Binding<Bool>) -> some View {
    ModifiedContent(content: self, modifier: MyViewModifier(binding: binding))
  }
}

在LoadingView中调用时传入Binding:

Text("test")
    .listen(to: $model.publisher)

方案二:在ViewModifier中持有ObservedObject实例

让ViewModifier直接持有MyModel实例,通过@ObservedObject维持生命周期绑定:

struct MyViewModifier: ViewModifier {
  @ObservedObject var model: MyModel
  
  func body(content: Content) -> some View {
    content
      .onReceive(model.$publisher) { value in
        print("received")
      }
  }
}

extension View {
  func listen(to model: MyModel) -> some View {
    ModifiedContent(content: self, modifier: MyViewModifier(model: model))
  }
}

在LoadingView中调用时传入model实例:

Text("test")
    .listen(to: model)

方案三:直接在View中使用onChange(最简洁)

如果不需要复用监听逻辑,可以直接在LoadingView中使用onChange,跳过自定义ViewModifier:

Text("test")
    .onChange(of: model.publisher) { value in
      print("received")
    }

内容的提问来源于stack exchange,提问作者Tometoyou

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 14:20:40