惰性属性被多次初始化的潜在影响是啥?

Posted

技术标签:

【中文标题】惰性属性被多次初始化的潜在影响是啥?【英文标题】:What are the potential repercussions of a lazy property getting initialised more than once?惰性属性被多次初始化的潜在影响是什么? 【发布时间】:2019-05-03 11:10:27 【问题描述】:

Apple 文档是这么说的

如果一个用惰性修饰符标记的属性被多个访问 同时线程并且该属性尚未初始化, 不能保证该属性只会被初始化一次。

我的问题是,一个属性被多次初始化的潜在影响是什么?

如果一个属性被多次初始化,会使用哪一个? Swift 如何管理它们?

我浏览了一些答案。

Is it normal that lazy var property is initialized twice?

但他们只是说惰性属性可以多次初始化。我想知道这会有什么影响。

提前致谢。

【问题讨论】:

损失是什么意思?数据完整性、性能、...? 任何负面影响……后果。所以在使用惰性属性之前我会三思而后行。 【参考方案1】:

(请参阅我对 rmaddy 的回答关于我对编写指针本身的线程安全性的担忧的评论。我的直觉是内存损坏是不可能的,但对象重复是。但我无法从文档中证明内存损坏是不可能的。)

如果惰性变量具有引用语义,则对象重复是 IMO 的主要问题。两个赛车线程可以获取不同的实例:

线程 1 开始初始化(对象 A) 线程 2 开始初始化(对象 B) 线程 1 将 A 分配给 var 并将 A 返回给调用者 线程 2 将 B 分配给 var 并将 B 返回给调用者

这意味着线程 1 和线程 2 有不同的实例。如果他们期望有相同的实例,那肯定是个问题。如果该类型具有值语义,那么这无关紧要(这是值语义的点)。但如果它有引用语义,那么这很可能是个问题。

IMO,lazy 如果可以使用多线程调用者,则应始终避免使用。它给对象构造将发生在哪个线程上带来了不确定性,而线程安全对象中您最不想要的就是不确定将在哪个线程代码上运行。

就我个人而言,我很少看到 lazy 的良好用例,除非您需要在您自己的属性之一的初始化程序中传递 self。 (即便如此,我通常使用 ! 类型而不是 lazy。)这样,lazy 实际上只是一个解决 Swift init 头痛的笨拙工作,我希望我们可以用另一种方式解决它,并取消lazy,IMO 具有与 ObjC 中的 @atomic 相同的“不能完全兑现其承诺,因此您可能必须编写自己的版本”的问题。

“延迟初始化”的概念仅在所讨论的类型构建起来非常昂贵且不太可能永远使用时才有用。如果在某个时候实际使用了该变量,它会变慢并且具有较少的确定性性能以使其变得懒惰,而且当它通常是只读的时,它会迫使您将其设为var

【讨论】:

【参考方案2】:

答案完全取决于你在惰性属性实现中的代码。最大的问题是您在代码中添加的任何副作用,因为它们可能会被多次调用。

如果你所做的只是创建一个独立的对象,初始化它,然后返回它,那么就不会有任何问题。

但是,如果还要执行诸如添加视图、更新数组或其他数据结构或修改其他属性之类的操作,那么如果多次创建惰性变量,您就会遇到问题,因为所有这些副作用都会发生不止一次一次。您最终会添加两个视图或将两个对象添加到数组中,等等。

确保你的惰性属性中的代码只创建和初始化一个对象,不执行任何其他操作。如果您这样做,那么如果从多个线程多次创建惰性属性,您的代码将不会导致任何问题。

【讨论】:

我们是否有关于属性赋值的线程安全的声明?具体来说,我们是否知道在存在两个竞速初始化程序的情况下,返回给所有读者的指针将是一个有效的指针?我一直觉得答案可能是肯定的(所以对象重复是唯一的问题),但我很难找到它作为 Swift 的承诺。 @RobNapier 好问题。我对 Swift 内部的了解不够多,无法知道指针线程安全的答案。

以上是关于惰性属性被多次初始化的潜在影响是啥?的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin类的初始化 ④ ( lateinit 延迟初始化 | ::属性名称.isInitialized 检查属性是否初始化 | lazy 惰性初始化 )

Kotlin类的初始化 ④ ( lateinit 延迟初始化 | ::属性名称.isInitialized 检查属性是否初始化 | lazy 惰性初始化 )

Swift 常量默认是惰性的吗?

无法在属性初始化程序中使用实例成员,将变量设置为惰性不起作用

Swift 中的惰性只读属性

带有 UIView 子类的惰性属性