强制解包已在同一代码行中选择性访问的变量是否安全


someFunction(completion: { [weak self] in
self?.variable = self!.otherVariable
})

总是安全的吗?我访问语句开头的可选self,我个人认为如果nilself,该语句的第二部分将永远不会执行。这是真的吗?如果self真的是nil,第二部分永远不会发生吗?而且,在这一行代码中,self永远不会被"遗忘"吗?

可选链接 摘自《The Swift Programming Language》 给出以下示例:

let john = Person()
// ...
let someAddress = Address()
// ...
john.residence?.address = someAddress

后跟(着重号后加):

在此示例中,尝试设置 john.residence 的地址属性将失败,因为 john.residence 当前为 nil。

赋值是可选链接的一部分,这意味着不会计算 = 运算符右侧的任何代码。

适用于您的案例:在

self?.variable = self!.otherVariable

如果selfnil评估右侧。 因此,您的问题的答案

如果自我真的是零,第二部分永远不会发生吗?

是"是"。关于第二个问题

这段代码中,自我永远不会被"无所事事"吗?

我最初的假设是,一旦self被确定为!= nil, 在对《公约》执行情况的评价过程中,对self!进行了有力的提及。 声明,这样就不会发生这种情况。然而(正如@Hamish指出的那样), 这不能保证。苹果工程师乔·格罗夫(Joe Groff)在确认操作顺序时写道 斯威夫特论坛:

这不能保证。发布可能会优化为早于此时间,直到上次正式使用强引用之后的任何时间点。由于为评估左侧weakProperty?.variable而加载的强引用之后不再使用,因此没有什么可以使其保持活动状态,因此可以立即释放。
如果变量的 getter 中有任何副作用导致weakProperty引用的对象被解除分配,从而消除弱引用, 那么这将导致右侧的强制展开失败。你应该使用 if let 来测试弱引用,并引用受if let约束的强引用

不,这不安全

正如 @Hamish 在下面的评论中指出的那样,Swift 编译器工程师 Joe Groff 描述说,不能保证在 RHS 评估期间持有强有力的参考文献。

确认操作顺序

Rod_Brown:

嘿,你好

我想知道弱变量上的一种访问类型的安全性:

class MyClass {
weak var weakProperty: MyWeakObject?
func perform() {
// Case 1
weakProperty?.variable = weakProperty!.otherVariable
// Case 2
weakProperty?.performMethod(weakProperty!)
}
}

对于上述两种情况,斯威夫特是否保证 在这些位置可以强制解开weakProperty

我很好奇 Swift 在访问期间做出的保证 可选链接 例如,weakProperty!访问器保证 只有触发 iff 可选链接首先确定该值是 已经非nil

?此外,弱对象是否保证保留 此评估的持续时间,或者弱变量可能是 能够在可选访问和正在 叫?

Joe_Groff:

这不能保证。发布可能会经过优化,以便更早发布 比这,到最后一次正式使用强者之后的任何一点参考。由于加载的强参考值以便进行评估 左侧weakProperty?.variable之后不再使用, 没有什么能让它活着,所以它可以立即 释放。如果变量的吸气剂有任何副作用 导致weakProperty引用的对象被解除分配,nil- 删除弱引用,那么这将导致 在右侧强制打开包装以失败。你应该使用 if 让来测试 弱引用,并引用由 if 绑定的强引用 让:

if let property = weakProperty {
property.variable = property.otherVariable
property.performMethod(property)
}

这应该更安全,也更有效,因为弱引用是 加载和测试一次,而不是四次。


鉴于上面引用的 Joe Groff 的答案,我之前的答案没有实际意义,但我将把它留在这里,作为一个可能有趣(尽管失败)的 Swift 编译器深处的旅程。


历史答案得出了一个不正确的最终论点,但仍然经历了一段有趣的旅程

我将根据我对@appzYourLife:已删除答案的评论来回答这个答案:

这纯粹是猜测,但考虑到有点接近 许多经验丰富的 Swift 核心开发人员与 C++:s 之间的联系 提升库,我假设weak引用被锁定在一个 在表达式的生命周期内很强,如果这分配/变异self的东西,很像明确使用的std::weak_ptr::lock()的C++对应方。

让我们看一下您的示例,其中self已被weak引用捕获,并且在访问赋值表达式的左侧时未nil

self?.variable = self!.otherVariable
/* ^             ^^^^^-- what about this then?
|
-- we'll assume this is a success */

我们可以看看 Swift 运行时中对weak(Swift) 引用的基本处理,swift/include/swift/Runtime/HeapObject.h具体来说:

/// Load a value from a weak reference.  If the current value is a
/// non-null object that has begun deallocation, returns null;
/// otherwise, retains the object before returning.
///
/// param ref - never null
/// return can be null
SWIFT_RUNTIME_EXPORT
HeapObject *swift_weakLoadStrong(WeakReference *ref);

这里的关键是评论

如果当前值是已开始释放的非空对象, 返回空值;否则,在返回之前保留对象

由于这是基于后端运行时代码注释,因此它仍然有些推测性,但我想说上述内容意味着,当尝试访问weak引用指向的值时,确实会在调用的生命周期内保留为强引用("...直到返回">)。


为了尝试从上面赎回"有点投机">的部分,我们可能会继续深入研究 Swift 如何通过weak引用处理值的访问。从下面的@idmean:s注释(研究生成的SIL代码以获取OP:s等示例)我们知道调用了swift_weakLoadStrong(...)函数。

因此,我们将首先研究swift_weakLoadStrong(...)函数在swift/stdlib/public/runtime/HeapObject.cpp中的实现,看看我们将从那里得到什么:

HeapObject *swift::swift_weakLoadStrong(WeakReference *ref) {
return ref->nativeLoadStrong();
}

我们从swift/include/swift/Runtime/HeapObject.h 中找到WeakReferencenativeLoadStrong()方法的实现

HeapObject *nativeLoadStrong() {
auto bits = nativeValue.load(std::memory_order_relaxed);
return nativeLoadStrongFromBits(bits);
}

从同一文件中,实现nativeLoadStrongFromBits(...)

HeapObject *nativeLoadStrongFromBits(WeakReferenceBits bits) {
auto side = bits.getNativeOrNull();
return side ? side->tryRetain() : nullptr;
}

继续沿着调用链,tryRetain()是一种HeapObjectSideTableEntry方法(这对于对象生命周期状态机至关重要),我们在swift/stdlib/public/SwiftShims/RefCount.h中找到它的实现

HeapObject* tryRetain() {
if (refCounts.tryIncrement())
return object.load(std::memory_order_relaxed);
else
return nullptr;
}

RefCounts类型的tryIncrement()方法的实现(此处通过它的typedef:ed 专用化实例调用)可以在与上面相同的文件中找到:

// Increment the reference count, unless the object is deiniting.
bool tryIncrement() {
...
}

我相信这里的注释足以让我们使用此方法作为终点:如果对象没有启动(我们在上面假设它没有,因为假设 OP:s 示例中的赋值lhs是成功的),对象的(强)引用计数将增加, 并且HeapObject指针(由强引用计数增量支持)将传递给赋值运算符。我们不需要研究在赋值结束时最终如何执行相应的引用计数递减,但现在除了推测之外,我们知道与weak引用关联的对象将在赋值的生命周期内保留为强对象,因为它在左侧访问它时尚未被释放/释放(在这种情况下,它的右侧永远不会被处理,如@MartinR:s答案中所述)。

文档明确指出,如果分配的左侧被确定为零,则不会评估右侧。 但是,在给定的示例中,self引用较,可能会在可选检查通过后立即释放(并作废),但就在强制解包发生之前,使整个表达式 nil-不安全。

这总是安全的吗

没有。你不是在做"弱强舞"。做吧!每当使用weak self时,都应安全地解开 Optional 的包装,然后仅参考解包的结果 — 如下所示:

someFunction(completion: { [weak self] in
if let sself = self { // safe unwrap
// now refer only to `sself` here
sself.variable = sself.otherVariable
// ... and so on ...
}
})

更正前:

我认为其他人已经比我更好地回答了你问题的细节。

但除了学习。如果你真的希望你的代码可靠地工作,那么最好这样做:

someFunction(completion: { [weak self] in
guard let _ = self else{
print("self was nil. End of discussion")
return
}
print("we now have safely 'captured' a self, no need to worry about this issue")
self?.variable = self!.otherVariable
self!.someOthervariable = self!.otherVariable
}

更正后。

多亏了MartinR在下面的解释,我学到了很多东西。

阅读这篇关于闭包捕获的伟大文章。我奴性地认为,每当你在括号中看到某些东西时[]这意味着它被捕获并且它的价值不会改变。但我们在括号中唯一要做的是,我们正在weak- 将它化并让我们自己知道它的价值可能会变得nil。如果我们做了类似[x = self]的事情,我们就会成功地捕获它,但是我们仍然会遇到一个问题,即持有一个强大的指针来self自身并创建一个记忆周期。(从某种意义上说,这很有趣,从创建内存周期到由于您弱化了值而被释放而导致崩溃的一条非常细的线)。

所以总结一下:

  1. [capturedSelf = self]

    创建内存周期。不好!

  2. [weak self] 
    in guard let _ = self else{
    return
    } 
    

    (如果您之后强制self解开包装,可能会导致崩溃)guard let是完全没用的。因为下一行,仍然self可以变得nil.不好!

  3. [weak self] 
    self?.method1()
    

    (如果您之后强制self解开包装,可能会导致崩溃。如果self是非nil,则会通过。如果selfnil,将安全失败。这很可能是您想要的。这很好

  4. [weak self] in 
    guard let strongSelf = self else{ 
    return
    } 
    

    如果self已解除分配,则将安全失败,如果未nil,则将继续。但它有点违背了目的,因为当它删除自己的引用时,你不需要与self进行通信。我想不出一个好的用例。这很可能是没用的!

最新更新