当 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的主要内容,如果未能解决你的问题,请参考以下文章
当应用的“位置”和“后台应用刷新”权限发生变化时,在后台收到通知