@StateObject 与 @ObservedObject 当外部传递但由视图拥有时

2024-01-16

基于这个答案:SwiftUI 中 ObservedObject 和 StateObject 有什么区别 https://stackoverflow.com/questions/62544115/what-is-the-difference-between-observedobject-and-stateobject-in-swiftui

苹果文档代码在这里:https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app

在 SwiftUI 应用程序中,@StateObject当以下情况时应使用属性包装器View实例化对象本身,以便在视图更新期间不会重新创建该对象。

如果该对象在其他地方实例化,则@ObservedObject应使用包装器代替。

然而,有一条细线让它有点不清楚:如果该对象在其他地方实例化,但“注入”到View然后视图是该对象的唯一所有者/持有者?应该是@StateObject or @ObservedObject?

示例代码来说明这一点:

import SwiftUI
import Combine
import Foundation


struct ViewFactory {
    func makeView() -> some View {
        let viewModel = ViewModel()
        return NameView(viewModel)
    }
}


final class ViewModel: ObservableObject {
    @Published var name = ""
    init() {}
}


struct NameView: View {

    // Should this be an `@ObservedObject` or `@StateObject`?
    @ObservedObject var viewModel: ViewModel

    init(_ viewModel: ViewModel) {
        self.viewModel = viewModel
    }

    var body: some View {
        Text(viewModel.name)
    }
}

基于这篇文章:https://www.hackingwithswift.com/quick-start/swiftui/whats-the-difference- Between-observedobject-state-and-environmentobject https://www.hackingwithswift.com/quick-start/swiftui/whats-the-difference-between-observedobject-state-and-environmentobject

@StateObject 和 @ObservedObject 之间有一个重要的区别,那就是所有权——哪个视图创建了对象,哪个视图只是监视它。

规则是这样的:无论哪个视图第一个创建对象都必须使用 @StateObject,告诉 SwiftUI 它是数据的所有者并负责保持数据的活动。所有其他视图必须使用 @ObservedObject,告诉 SwiftUI 他们想要观察对象的更改,但不直接拥有它。

看来如果View实例化ViewModel,它必须声明为@StateObject。我的代码非常相似,唯一的区别是ViewModel是在其他地方创建的,但是View初始化后“拥有”它。


这是一个非常有趣的问题。这里发生了一些微妙的行为。

首先,请注意你不能只是改变@ObservedObject to @StateObject in NameView。它不会编译:

struct NameView: View {
    @StateObject var viewModel: ViewModel

    init(_ viewModel: ViewModel) {
        self.viewModel = viewModel
        //   ^ ???? Cannot assign to property: 'viewModel' is a get-only property
    }
    ...
}

要使其编译,您必须初始化底层_viewModel类型的存储属性StateObject<ViewModel>:

struct NameView: View {
    @StateObject var viewModel: ViewModel

    init(_ viewModel: ViewModel) {
        _viewModel = .init(wrappedValue: viewModel)
    }
    ...
}

但那里隐藏着一些东西。StateObject.init(wrappedValue:)声明如下:

public init(wrappedValue thunk: @autoclosure @escaping () -> ObjectType)

所以作为参数给出的表达式(只是viewModel上面)被包裹在一个闭包中,并且是not立即评价。该闭包被存储以供以后使用,这就是为什么它是@escaping.

正如您可能从圈子中猜到的那样,我们必须跳过才能使其编译,这是一种奇怪的使用方式StateObject。正常使用是这样的:

struct NormalView: View {
    @StateObject var viewModel = ViewModel()

  var body: some View {
        Text(viewModel.name)
    }
}

And 以奇怪的方式来做有一些缺点。要了解其缺点,我们需要了解其背景makeView() or NormalView()被评估。假设它看起来像这样:

struct ContentView: View {
    @Binding var count: Int

    var body: some View {
        VStack {
            Text("count: \(count)")
            NormalView()
            ViewFactory().makeView()
        }
    }
}

When count的值发生变化,SwiftUI 会询问ContentView为其body再次,这将评估两者NormalView() and makeView() again.

So body calls NormalView()在第二次评估期间,这会创建另一个实例NormalView. NormalView.init创建一个闭包,调用ViewModel(),并将闭包传递给StateObject.init(wrappedValue:). But StateObject.init does not立即评估此关闭。它将其存储起来以供以后使用。

Then body calls makeView(), which does call ViewModel()立即地。它通过了新ViewModel to NameView.init,它包装了新的ViewModel在闭包中并将闭包传递给StateObject.init(wrappedValue:). This StateObject也不会立即评估关闭,但新的ViewModel无论如何已经创建了。

一段时间后ContentView.body返回,SwiftUI 要调用NormalView.body。但在此之前,必须确保StateObject在这个NormalView has a ViewModel。它注意到这NormalView正在取代之前的NormalView在视图层次结构中的相同位置,因此它检索ViewModel之前使用过的NormalView并将其放入StateObject新的NormalView. It does not执行给定的闭包StateObject.init, so it 不创建新的ViewModel.

甚至后来,SwiftUI 想要调用NameView.body。但在此之前,必须确保StateObject在这个NameView has a ViewModel。它注意到这NameView正在取代之前的NameView在视图层次结构中的相同位置,因此它检索ViewModel之前使用过的NameView并将其放入StateObject新的NameView. It does not执行给定的闭包StateObject.init,所以它不使用ViewModel该闭包引用的。但是ViewModel无论如何都被创建了。

所以你使用的奇怪方式有两个缺点@StateObject:

  1. 你正在创建一个新的ViewModel每次你打电话makeView,尽管如此ViewModel可能永远不会被使用。这可能会很贵,具体取决于您的ViewModel.
  2. 您正在创建ViewModelContentView.bodygetter 正在运行。如果创建ViewModel有副作用,这可能会让 SwiftUI 感到困惑。 SwiftUI 期望bodygetter 是一个纯函数。在里面NormalView情况下,SwiftUI 正在调用StateObject在已知时间关闭,以便更好地处理副作用。

那么,回到你原来的问题:

应该是@StateObject or @ObservedObject?

嗯,哈哈,如果没有看到一个不那么玩具的例子,这个问题很难回答。但如果你确实需要使用@StateObject,您可能应该尝试以“正常”方式初始化它。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

@StateObject 与 @ObservedObject 当外部传递但由视图拥有时 的相关文章

  • iOS、通用链接、Swift。 continueUserActivity 未调用

    我正在为我们的 iOS 应用程序实现通用链接 这是我的一小部分 AppDelegate private func application application UIApplication openURL url URL sourceApp
  • 从 UIPickerView 的选定行设置 UIButton 的标题

    详细场景是这样的 我使用循环创建 10 个按钮并设置 0 9 的标签 点击每个按钮时 我将调用 UIPickerView 在其中加载来自不同数组的数据 到这里我就得到了预期的结果 但我希望 pickerView 中选定的行应设置为相应按钮的
  • Objective-C UILabel 作为超链接

    我正在尝试做一个UILabel一个链接UIWebView 我怎样才能做一个UILabel作为超链接 您可以使用 UITapGestureRecognizer 它将实现与您想要的类似的功能 UILabel myLabel UILabel al
  • 每 24 小时触发一次方法

    我正在尝试每天在给定时间触发一个方法 我尝试了一些方法 但我无法真正使其发挥作用 任何意见 将不胜感激 此外 如果无论应用程序是否打开它都会触发 那就更理想了 这可能吗 UI本地通知 http developer apple com lib
  • iOS 中 NSDecimalNumber 的小数分隔符错误

    我尝试通过以下方式输出具有正确的小数分隔符的十进制数的描述 NSString strValue 9 94300 NSDecimalNumber decimalNumber NSDecimalNumber decimalNumberWithS
  • Objective-C NSString for 循环与characterAtIndex

    我试图逐个字符地循环遍历 NSString 但出现 EXC BAD ACCESS 错误 您知道如何正确执行此操作吗 我已经在谷歌上搜索了几个小时但无法弄清楚 这是我的代码 m self textLength self text length
  • 从命令行添加 Xcode 开发者帐户

    我正在尝试使用xcodebuild allowProvisioningUpdates在我只能通过命令行访问的计算机 Azure Devops macOS 托管计算机 上 不幸的是 根据man xcodebuild为了使用 allowProv
  • 用户验证 Facebook 后未调用应用程序打开 Url 方法

    我已将 ios 应用程序中的 facebook 升级到 3 0 并使用提供的代码https developers facebook com docs howtos login with facebook using ios sdk http
  • iOS 7 上 Safari 浏览器的用户代理

    我只想在带有 Safari 浏览器的 iPhone 和 iPod 中打开我的网站 对于 Chrome Dolphin 等任何其他浏览器 它不应该打开 但目前我从几乎所有设备获得相同的用户代理 对于Safari User Agent Stri
  • 贴纸包会在模拟器上使 iMessage 崩溃,但在 iPhone 上不会崩溃

    按照 Apple 的在线说明和视频在 Xcode 中创建了一个贴纸包 所有图像的尺寸均正确且远低于文件大小阈值 如果我在我的实体 iPhone 上构建并运行贴纸包 一切都会完美运行 如果我在模拟器上构建并运行贴纸包 对于任何模拟的 iPho
  • 如何在button.addTarget操作中发送多个按钮?斯威夫特3

    如何将button和button2发送到我的pressButton2函数中 当用户触摸按钮2时 我需要更改按钮和按钮2的颜色 当我的 button2 addTarget 看起来像这样时 我收到错误 表达式列表中存在预期表达式 import
  • 如何在代码中编辑约束

    我有一个以 100 开始宽度限制的网页 当用户单击按钮时 我想将约束更改为 200 我试过这个 NSLayoutConstraint constrain NSLayoutConstraint constraintWithItem self
  • 在 UIWebView 中播放 Facebook 视频

    有谁知道如何在 Facebook 上播放视频UIWebView 我的应用程序将视频上 传到 Facebook 并检索视频的网址 我想将此网址嵌入到UIWebView播放 我已经为 youtube 解决了这个问题 但没有为 Facebook
  • TableViewController 的 viewDidLoad 未触发

    我一直在关注这个tutorial http www appcoda com ios programming sidebar navigation menu 有一个滑出式菜单 我添加了一个 TableViewController 它将显示文章
  • 使用 MapKit 的地形和卫星视图

    我是 Mapkit View 的新手 当我给出没有目的地的纬度和经度时 我想显示 MapKit中是否可以通过地形 卫星视图来显示地图 有教程链接吗 我看过一些访问 Google 地图 API html 文件 的示例 有必要吗 或者您可以通过
  • 叠加 SKScene 未显示

    我正在尝试将 SKScene 覆盖在 SCNScene 上 当我在模拟器和 iPhone6 上运行我的应用程序时 overlayScene SKScene 按预期显示 但是当我尝试在 iPhone5 上运行它 尝试了 2 个不同的设备 时
  • 如何使用 RX 应用宽限时间?

    我有一个Observable
  • 在 Swift 中从 Parse 加载图像

    我成功地将数据从 Parse 提取到 swift 中 但我的图像似乎没有按照我的方式工作 在我的 cellForRowAtIndexPath 方法中 我执行以下操作 var event AnyObject eventContainerArr
  • 像 TraceGL 一样分析 Objective C 中的代码路径?

    TraceGL 是一个非常简洁的项目 它允许 JS 程序员跟踪 Javascript 中的代码路径 它看起来像这样 我想为 Objective C 构建类似的东西 我知道运行时使跟踪方法调用变得相当容易 但是我如何跟踪控制流 例如 在上面的
  • 隐藏选项卡栏项目并对齐其他选项卡项目

    在我的应用程序中 我有 4 个选项卡栏项目 我正在 XIB 文件中添加这 4 个选项卡栏项目 最初我必须显示 3 个选项卡栏项目 同步后我必须在我的应用程序中显示第 4 个选项卡栏项目 因此 为此 我使用以下代码隐藏第四个选项卡栏项目 se

随机推荐