强迫在同一行代码中可选访问的解包variables是否安全?

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

总是安全吗? 我在声明的开始部分访问可选self ,并且亲自假设如果self nil ,则此声明的第二部分将永远不会执行。 这是真的? 如果self确实是nil ,第二部分将永远不会发生? 在这段单行代码中, self不会被“扼杀”的呢?

来自“Swift编程语言”的可选链接给出以下示例:

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

其次(强调加):

在这个例子中,尝试设置john.residence的地址属性将失败,因为john.residence当前为零。

该赋值是可选链接的一部分,这意味着=运算符右侧没有任何代码被评估。

适用于你的情况:在

 self?.variable = self!.otherVariable 

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

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

是是的”。 关于第二个问题

在这段单行代码中,自己是不会被“扼杀”的呢?

认为 ,一旦self被确定为!= nil ,强烈的self! 是在整个声明的评价过程中进行的,所以这是不可能发生的。 (现在在dfri的回答中已经确认了。)

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

这是纯粹的推测,但考虑到许多经验丰富的Swift核心开发人员和C ++:Boost lib之间有些密切的联系,我会假设weak引用被locking到expression式生命周期中的强引用,如果这个赋值/变异与self中的东西一样,很像C ++对象的明确使用的std::weak_ptr::lock()

让我们来看看你的例子, self被一个weak引用捕获,并且在访问赋值expression式的左边时不是nil

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

我们可以看一下Swift运行时swift/include/swift/Runtime/HeapObject.hweak (Swift)引用的基本处理:

 /// 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); 

这里的关键是评论

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

由于这是基于后端运行时代码注释的,所以它仍然有点推测性,但是我想说上面这个意思是说,当试图访问weak引用所指向的值的时候,那里的引用确实会被保留为一个强大的引用( “…直到返回” )。


为了试图赎回上面的“有点投机”的部分,我们可以继续深入探究Swift如何通过weak引用来处理一个值的访问。 从@idmean:下面的注释 (学习生成的SIL代码,例如OP:s),我们知道调用了swift_weakLoadStrong(...)函数。

所以我们首先看一下swift/stdlib/public/runtime/HeapObject.cpp swift_weakLoadStrong(...)函数的实现,看看我们从哪里得到:

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

我们从swift/include/swift/Runtime/HeapObject.hfindWeakReferencenativeLoadStrong()方法的swift/include/swift/Runtime/HeapObject.h

 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 (对于对象生命周期状态机是必不可less的 )的方法,我们在swift/stdlib/public/SwiftShims/RefCount.hfind它的实现

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

RefCountstypes的tryIncrement()方法(这里通过typedef :ed的特化实例调用)的实现可以在与上面相同的文件中find:

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

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

这是否安全?

没有 。 你不是在做“弱强舞”。 做吧! 每当你使用weak self ,你应该安全地展开选项,然后只参考那个展开的结果 – 像这样:

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

文件清楚地表明 ,如果任务的左侧被确定为零,则右侧将不被评估。 然而,在给定的例子中, self弱引用 ,并且在可选检查通过之后可以被释放(并且是无效的),但是在force-unwrap将会发生之前,使得整个expression式不是不安全的。

更正前:

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

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

 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的解释,我学到了很多东西。

从closures捕获这个伟大的职位阅读。 每当你看到括号中的东西时,我都会粗暴地想,这意味着它被捕获,其值不会改变。 但是我们在括号里面唯一的做法是,我们正在弱化它,让我们知道它的价值可能会变成nil 。 如果我们做了类似[x = self]事情,我们就可以成功捕获它,但是我们仍然有一个强大的指向自身的指针,并创build一个内存循环。 (有趣的是,从创build一个内存循环到创build崩溃的一个非常细致的线条,由于值被释放,因此值被释放)。

所以得出结论:

  1. [capturedSelf = self] (创build记忆周期)
  2. [weak self] in guard let _ = self else {return} (如果你强制self打开后可能导致崩溃)
  3. [weak self] in guard let strongSelf = self else { return} (如果self被释放,或者如果不是nil就会安全地失败)

根据您的需要,您应该在选项2或3之间进行一些操作