SwiftUI中.onChange闭包添加[t]捕获列表的作用是什么
.onChange 中[t]捕获列表的作用与原理 不少开发教程会建议在SwiftUI的.onChange闭包中添加[t]捕获列表,示例代码如下:
.onChange(of: t) { [t] newValue in let oldValue = t if newValue % oldValue == 2 { print("2") } else { print("not 2") } }
很多开发者实际编码时移除[t]捕获列表,感知不到任何运行效果差异,其实这个写法不是什么SwiftUI的特殊语法,就是Swift闭包捕获列表的常规用法,早期教程普遍推荐这么写有明确的历史背景,感知不到差异只是没碰到会出问题的场景而已。
核心作用
Swift闭包默认会对外部变量做引用捕获,也就是说闭包里读到的变量值,会跟着外部变量的实时变化走。加[t]捕获列表的本质是:
- 当
t是Int、String、结构体这类值类型时,会在闭包创建的瞬间拷贝一份t的当前值存下来,之后不管外部的t被改成什么样,闭包里拿到的永远是这份固定的快照值,不会随外部变化。 - 当
t是类实例这类引用类型时,会固定住闭包创建瞬间t指向的实例,之后就算外部变量t被重新赋值指向其他对象,闭包里拿到的还是最初那个实例,不会跟着变。
教程推荐这个写法的核心原因
这个写法能大规模流行,本质是填早期SwiftUI的API设计坑:iOS14、iOS15两个版本的.onChange接口,只会给闭包回传变更后的新值newValue,根本不提供旧值参数。
这里有个很多新手踩过的细节:onChange触发闭包执行的时候,外层被监听的
t已经被更新成新值了。这时候你如果不加[t]提前把旧值截获,直接在闭包里写let oldValue = t,拿到的根本不是变更前的值,是和newValue完全一样的新值,所有涉及新旧值比对的逻辑都会出错。
你看到的示例代码就是典型的新旧值比对场景,在iOS14/15环境下去掉[t]的话,oldValue永远等于newValue,newValue % oldValue永远是0,永远打印不出"2"。只是现在大部分开发者测试用的都是iOS16及以上系统,用的是自带oldValue参数的新版onChange接口,不会在闭包里直接读外层的t,自然碰不到这个bug。
为什么去掉[t]你感知不到差异
绝大多数人觉得加不加没区别,基本都是这几个原因:
- 项目最低部署版本是iOS16+,用的是系统原生返回新旧两个值的新版
.onChange,不需要自己从外层读t拿旧值,加不加捕获都不影响逻辑。 - 闭包里的逻辑全是同步执行的,从onChange触发到代码跑完只有几毫秒,这段时间
t没有发生第二次修改,直接读外层t和读捕获的快照值刚好相等。 - 测试场景没有覆盖异步逻辑:如果闭包里写了
Task、延迟执行、网络请求回调这类异步代码,等异步块实际执行的时候,t可能已经被改了好几次,这时候不加捕获拿到的t是完全不可控的最新值,和你想要的「触发本次变更时的值」差很远,差异会非常明显。
现在编码还要强制加这个捕获吗
不用死记规则,看场景判断就行:
- 还在兼容iOS15及以下系统、用老版onChange需要拿旧值的,必须加,不然旧值会拿错导致逻辑异常。
- 闭包里有异步逻辑、需要用到触发本次变更时的
t值的,不管什么系统版本都建议加,避免异步执行时读到被修改的脏值。 - 如果是iOS16+用新版onChange接口,闭包全是同步逻辑也不需要跨时机持有
t值,加不加都可以,不会有功能问题。
内容的提问来源于stack exchange,提问作者LiangWang

