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

Sti*_*Sti 38 null memory-management optional ios swift

someFunction(completion: { [weak self] in
    self?.variable = self!.otherVariable
})
Run Code Online (Sandbox Code Playgroud)

总是安全吗?我self在语句的开头访问了可选项,并且我个人认为如果self是,那么该语句的第二部分将永远不会被执行nil.这是真的?如果self确实如此nil,第二部分永远不会发生?self在这一行代码中,它永远不会被"填充"?

Mar*_*n R 25

"Swift编程语言"中的可选链接给出了以下示例:

 let john = Person()
 // ...
 let someAddress = Address()
 // ...
 john.residence?.address = someAddress
Run Code Online (Sandbox Code Playgroud)

其次是(强调补充):

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

赋值是可选链接的一部分,这意味着不会对=运算符右侧的代码进行求值.

适用于您的案例:In

self?.variable = self!.otherVariable
Run Code Online (Sandbox Code Playgroud)

如果是,则不评估右侧.因此,你的问题的答案selfnil

如果自我确实是零,第二部分永远不会发生?

是是的".关于第二个问题

在这一行代码中,自我可能永远不会被"掏空"吗?

我最初的假设是,一旦self确定了!= nil,self!在整个评估声明中就会有一个强有力的参考,这样就不会发生这种情况.但是(正如@Hamish指出的那样),这并不能保证.Apple工程师Joe Groff 在Swift论坛中的确认操作顺序中写道:

这不保证.版本可以优化为在此之前发生,在最后一次正式使用强引用之后的任何时刻.由于为了评估左侧而加载的强引用weakProperty?.variable后来没有使用,因此没有任何东西可以保持它存活,所以它可以立即释放.
如果getter中的变量有任何副作用导致被引用的对象weakProperty被释放,则输出弱引用,那么这将导致右侧的force-unwrap失败. 你应该使用if来测试弱引用,并引用由该引用绑定的强引用if let

  • 我认为这是正确的答案.如果左侧是任何赋值操作的"nil",则不会评估右侧.考虑这样的事情:`instance?.val =([] as [Int])[0]`(假设`val`是`Int`).该表达式的右侧将导致崩溃,但如果`instance`为`nil`则不会进行评估. (2认同)

dfr*_*fri 16

不,这不安全

正如@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!)
    }
}
Run Code Online (Sandbox Code Playgroud)

对于上面两种情况,Swift是否保证 weakProperty可以在这些位置展开力?

我很好奇Swift在可选链接期间对访问的保证例如,weakProperty!如果可选链接首先确定该值已经是非链接,那么访问器只能保证触发nil吗?

此外,是否保证在此评估期间保留弱对象,或者弱变量是否可能在可选访问和被调用方法之间解除分配?

Joe_Groff:

这不保证.版本可以优化为在此之前发生,在最后一次正式使用强引用之后的任何时刻.由于为了评估左侧而加载的强引用weakProperty?.variable后来没有使用,因此没有任何东西可以保持它存活,所以它可以立即释放.如果getter中的变量有任何副作用导致被引用的对象weakProperty被释放,则 nil输出弱引用,则会导致右侧的force-unwrap失败.你应该使用if来测试弱引用,并引用if let绑定的强引用:

if let property = weakProperty {
  property.variable = property.otherVariable
  property.performMethod(property)
}
Run Code Online (Sandbox Code Playgroud)

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


考虑到乔Groff上面回答的回答,我之前的回答没有实际意义,但我会把它留在这里作为一个可能有趣(虽然失败)的旅程进入Swift编译器的深度.


尽管如此,历史答案还是通过一个有趣的旅程达到了正确的最终论证

我将根据我对@appzYourLife的评论得出这个答案:删除的答案:

这是纯粹的推测,但考虑到许多经验丰富的Swift核心开发人员与C++:Boost lib之间的密切联系,我会假设weak在表达式的生命周期中,引用被锁定为强大的引用,如果这指定/变异某事in self,就像明确使用 std::weak_ptr::lock() 的C++对应物一样.

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

self?.variable = self!.otherVariable
/* ^             ^^^^^-- what about this then?
   |
    \-- we'll assume this is a success */
Run Code Online (Sandbox Code Playgroud)

我们可以weak在Swift运行时查看(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);
Run Code Online (Sandbox Code Playgroud)

这里的关键是评论

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

由于这是基于后端运行时代码注释,它仍然有点推测,但我会说上面暗示当试图访问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();
}
Run Code Online (Sandbox Code Playgroud)

我们找到了from nativeLoadStrong()方法的实现WeakReferenceswift/include/swift/Runtime/HeapObject.h

HeapObject *nativeLoadStrong() {
  auto bits = nativeValue.load(std::memory_order_relaxed);
  return nativeLoadStrongFromBits(bits);
}
Run Code Online (Sandbox Code Playgroud)

同一个文件中,执行nativeLoadStrongFromBits(...):

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

继续沿着调用链,tryRetain()是一种方法HeapObjectSideTableEntry(对于对象生命周期状态机是必不可少),我们发现它的实现方式swift/stdlib/public/SwiftShims/RefCount.h

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

类型的tryIncrement()方法的实现RefCounts(这里通过它的一个实例调用typedef:ed的特化)可以在上面的同一个文件中找到:

// Increment the reference count, unless the object is deiniting.
bool tryIncrement() {
  ...
}
Run Code Online (Sandbox Code Playgroud)

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