强迫在同一行代码中可选访问的解包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
如果self
是nil
则不评估右侧。 因此,你的问题的答案
如果自己确实是零,第二部分将永远不会发生?
是是的”。 关于第二个问题
在这段单行代码中,自己是不会被“扼杀”的呢?
我认为 ,一旦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.h
中weak
(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.h
findWeakReference
的nativeLoadStrong()
方法的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.h
find它的实现
HeapObject* tryRetain() { if (refCounts.tryIncrement()) return object.load(std::memory_order_relaxed); else return nullptr; }
RefCounts
types的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崩溃的一个非常细致的线条,由于值被释放,因此值被释放)。
所以得出结论:
-
[capturedSelf = self]
(创build记忆周期) -
[weak self] in guard let _ = self else {return}
(如果你强制self
打开后可能导致崩溃) -
[weak self] in guard let strongSelf = self else { return}
(如果self
被释放,或者如果不是nil
就会安全地失败)
根据您的需要,您应该在选项2或3之间进行一些操作