Swift中didSet在数组多次append时仅首次触发问题排查
嘿,我来帮你拆解这个问题——你遇到的情况其实是两个关键细节导致的,咱们一步步理清楚:
1. 为什么你觉得第二次append没触发didSet?
其实第二次append大概率是触发了didSet的,只是你的didSet逻辑里有个致命的小问题:当循环到已经存在于jsFiles中的元素时,直接执行了return,导致后续元素根本没被处理。
看你的这段代码:
didSet { for (string, time) in additionalJSFilesToRun { guard !self.jsFiles.contains(string) else { return } // 这里的return会直接退出整个didSet // ... 加载JS的代码 } }
第一次append后,additionalJSFilesToRun是[("ABC", .atDocumentStart)],循环处理ABC并把它加入jsFiles。第二次append后,数组变成[("ABC", ...), ("DEF", ...)],循环第一个元素ABC时,发现它已经在jsFiles里,直接return退出了didSet,DEF的代码完全没执行,所以你会觉得JS没加载,甚至误以为didSet没触发。
2. 补充:didSet的触发逻辑
顺便纠正一个常见误区:Swift中的Array是值类型,不是引用类型。所以调用append、remove这类修改数组内容的操作,本质上是创建了一个新的数组实例并赋值给变量,因此会触发didSet。只有当你修改的是引用类型的内部状态(比如自定义类的属性),而没有改变变量本身的引用时,didSet才不会触发。
如何修复你的代码?
你需要调整didSet里的逻辑,避免提前退出,这里给你两个方案:
方案一:用continue替代return(简单直接)
把退出整个didSet的return改成跳过当前元素的continue,这样遇到已存在的元素时,只会跳过它,继续处理后续元素:
didSet { for (string, time) in additionalJSFilesToRun { guard !self.jsFiles.contains(string) else { continue // 跳过已存在的元素,继续处理下一个 } guard let jsPath = Bundle.main.path(forResource: string, ofType: "js"), let jsSource = try? String(contentsOfFile: jsPath) else { continue // 加载失败也跳过,不影响其他元素 } let script = WKUserScript(source: jsSource, injectionTime: time, forMainFrameOnly: false) self.webView.configuration.userContentController.addUserScript(script) jsFiles.insert(string) } }
方案二:只处理新增元素(更高效)
如果数组元素较多,遍历整个数组会有冗余操作,可以只处理本次新增的元素(当前数组减去旧数组的部分):
didSet { // 找出新增的元素:当前数组有但旧数组没有的(通过文件名判断) let newElements = additionalJSFilesToRun.filter { element in !oldValue.contains { $0.0 == element.0 } } for (string, time) in newElements { guard !self.jsFiles.contains(string) else { continue } guard let jsPath = Bundle.main.path(forResource: string, ofType: "js"), let jsSource = try? String(contentsOfFile: jsPath) else { continue } let script = WKUserScript(source: jsSource, injectionTime: time, forMainFrameOnly: false) self.webView.configuration.userContentController.addUserScript(script) jsFiles.insert(string) } }
修改后,你可以在didSet的第一行加个断点或者print("didSet triggered"),验证第二次append确实触发了didSet,DEF对应的JS也会正常加载了。
内容的提问来源于stack exchange,提问作者user3338275

