当 Struct 的特定属性发生突变时通知父级,Swift

Posted

技术标签:

【中文标题】当 Struct 的特定属性发生突变时通知父级,Swift【英文标题】:Notifying parent when particular property of Struct is mutated, Swift 【发布时间】:2021-06-26 03:18:34 【问题描述】:

我正在尝试实现这样的目标:

struct InputField
    var input: String
        didSet
            if let onChangeValue =  onValueChange
                onChangeValue()
            
            if let errorMsgAfterValidation = errorMsgAfterValidation
                self.errorMsg = errorMsgAfterValidation()
            
        
    
    
    var errorMsg: String = ""
    var onValueChange: (() -> ())?
    var errorMsgAfterValidation: (() -> (String))?


class FormViewModel
    init() 
        self.nameInputField = InputField(input: "")
        self.setupFormFields()
    
    
    var nameInputField: InputField
    
    func setupFormFields()
        self.nameInputField.onValueChange = 
            //Custom cross felds Logic, ex: Load Name suggestions
        
        self.nameInputField.errorMsgAfterValidation = 
            //Form field validation
            // ISSUE: self.nameInputField.input is not the latest one
            return validateName(self.nameInputField.input)
        
    

这里的“输入”会随着用户在文本字段中输入而发生变化。

问题:内部闭包 self.nameInputField 实例/值不是最新的。后面是一个突变。如果我将“self”作为闭包参数传​​递,则它不等同于父 FormViewModel 持有的 self.nameInputeField。

nameInputField 上的didSet 在闭包执行后被调用。

我想要什么:我的输入字段将通知 FormViewModel 并询问错误消息。 FormViewModel 应该与输入字段正确同步。由于其他一些原因,我也无法使用课程。

一种解决方案是删除两个闭包并将其中的逻辑移动到 nameInputField 的 didSet,但我有 10 个奇怪的字段,看起来不太好。

【问题讨论】:

【参考方案1】:

值类型总是不可变的。

Int 更容易理解。

class A 
    var value: Int = 1 
        didSet 
            print("value changed", oldValue, value)
        
    


let a = A()
a.value = 2
// prints "value changed 1 2"

即使value 通过使用var 是“可变的”,它也不是。每次修改value,swift都会生成一个新的Int,并用它替换value。您的didSet 观察者在此替换完成后被调用。

结构有点混乱,但它们也是值类型。

struct S 
    
    var value: String = "" 
        didSet 
            print("value changed", oldValue, value)
        
    


class A 
    
    var s: S = S() 
        didSet 
            print("s changed", oldValue, s)
        
    


let a = A()
a.s.value = "hi"
// prints "value changed  hi"
// prints "s changed S(value: "") S(value: "hi")"
    结构体的任何属性更改都会导致结构体本身被新结构体替换(使用更新后的属性从结构体中复制)。 它按以下顺序发生:
      属性已更改 属性didSet观察者被调用 结构已更改 struct didSet 调用观察者

所以在第 2 步中,您的 errorMsgAfterValidation 正在执行,结构本身甚至还没有更新。这就是为什么“这是一个突变”。

事实上,您的代码在我刚刚创建的 Xcode 游乐场中导致了运行时异常。

imultaneous accesses to 0x600003898ab0, but modification requires exclusive access.
Previous access (a modification) started at  (0x105563ff4).
Current access (a read) started at:
0    libswiftCore.dylib                 0x00007fff2ff7be50 swift_beginAccess + 568
6    MyPlayground                       0x00000001045de150 main + 0
7    CoreFoundation                     0x00007fff20390114 __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 12
8    CoreFoundation                     0x00007fff2038f382 __CFRunLoopDoBlocks + 434
9    CoreFoundation                     0x00007fff20389bc1 __CFRunLoopRun + 899
10   CoreFoundation                     0x00007fff2038949f CFRunLoopRunSpecific + 567
11   GraphicsServices                   0x00007fff2c257d28 GSEventRunModal + 139
12   UIKitCore                          0x00007fff24696967 -[UIApplication _run] + 912
13   UIKitCore                          0x00007fff2469bb43 UIApplicationMain + 101
14   MyPlayground                       0x00000001045de150 main + 194
15   libdyld.dylib                      0x00007fff2025a3e8 start + 1
Fatal access conflict detected.

要快速解决您的问题,请修改您的errorMsgAfterValidation 以接受String 参数,并将当前的input 传递给它,这样您就不会在此闭包中引用self.nameInputField

struct InputField
    var input: String
        didSet
            if let onChangeValue =  onValueChange
                onChangeValue()
            
            if let errorMsgAfterValidation = errorMsgAfterValidation
                self.errorMsg = errorMsgAfterValidation(input)
            
        
    
    
    var errorMsg: String = ""
    var onValueChange: (() -> ())?
    var errorMsgAfterValidation: ((String) -> (String))?


class FormViewModel
    init() 
        self.nameInputField = InputField(input: "")
        self.setupFormFields()
    
    
    var nameInputField: InputField
    
    func setupFormFields()
        self.nameInputField.onValueChange = 
            //Custom cross felds Logic, ex: Load Name suggestions
        
        self.nameInputField.errorMsgAfterValidation =  input in
            return validateName(input)
        
    


没有更多信息,很难给出更好的解决方案。

【讨论】:

这基本上是我遇到这个问题时的推断。但问题是我可能需要在 errorMsgAfterValidation 中访问整个结构。我可能想要更改该闭包内的其他属性。我可以在关闭时传入“self”,但我不认为持有过时的结构是安全/良好的做法。我要做的是,使用 onMutate(oldValue: Self) 方法创建一个协议“NestedMutableStruct”,该方法将从结构的 didSet 中调用。这个方法会调用闭包。 附加上下文:我将有一个包含多个部分的表单,其中包含多个字段。部分和字段都是基于结构的模型。我希望在任何部分中任何字段的任何“输入”发生更改时进行回调,然后相应地处理它们。

以上是关于当 Struct 的特定属性发生突变时通知父级,Swift的主要内容,如果未能解决你的问题,请参考以下文章

让 div 跟随光标在特定的 div 父级中

Swift Struct 实例在更新其值时发生更改

当应用的“位置”和“后台应用刷新”权限发生变化时,在后台收到通知

托管对象发生故障时如何正确处理 KVO 通知?

当数据库中的某些内容被修改时,仅通过 WebSockets 通知特定用户

当我修改克隆的 obj(使用 ...)时,为啥 obj 会发生突变?