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

时间:2017-07-26 12:37:59

标签: ios swift memory-management null optional

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

总是安全吗?我在语句的开头访问了可选的self,并且我个人认为如果selfnil,则永远不会执行此语句的第二部分。这是真的?如果self确实是nil,那么第二部分永远不会发生?并且self可能永远不会发生'#1}}在这一行代码中?

5 个答案:

答案 0 :(得分:25)

Optional Chaining 来自“Swift编程语言”  给出以下示例:

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

接着(强调补充):

  

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

     

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

适用于您的案例:

self?.variable = self!.otherVariable

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

  

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

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

  

在这一行代码中,自我永远不会被“掏空”吗?

我原来的假设self一旦被确定为!= nil, 在整个评估过程中都会对self!提出强烈的引用 声明,这样就不会发生。然而(正如@Hamish指出的那样), 这不保证。 Apple工程师Joe Groff在Confirming order of operations写道 斯威夫特论坛:

  

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

绑定的强引用

答案 1 :(得分:16)

不,这不安全

正如@Hamish在下面的评论中指出的那样,Swift编译工程师Joe Groff describes无法保证在RHS的持续时间内保留强大的参考资料。评估[强调我的]

  

确认操作顺序

     

<强> Rod_Brown:

     

你好,

     

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

class MyClass {

    weak var weakProperty: MyWeakObject?

    func perform() {
        // Case 1
        weakProperty?.variable = weakProperty!.otherVariable

        // Case 2
        weakProperty?.performMethod(weakProperty!)
    }
}
     

上面两种情况,是由Swift保证的   weakProperty可以在这些位置强行打包吗?

     

我很好奇Swift在访问过程中提供的保证   可选链接例如是weakProperty!访问者保证   如果可选链接首先确定该值,则仅触发if   已经非nil

     

此外,保证弱对象保留   评估的持续时间,或弱变量可能是   能够在可选访问和方法之间解除分配   叫?

     

<强> Joe_Groff:

     

这不是保证。可以优化版本以更早发生   比这个,在最后一次正式使用强者之后的任何一点   参考。 自加载强引用以进行评估   之后不使用左侧weakProperty?.variable,   没有什么可以让它保持活力,所以它可以立即生效   释放。如果变量的getter中有任何副作用   导致weakProperty引用的对象被释放,   nil - 输出弱引用,然后会导致   强行解开右侧失败。你应该使用if let来测试   弱引用,并引用由if约束的强引用   让:

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

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

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

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

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

  

这是纯粹的猜测,但考虑到有点接近   许多经验丰富的Swift核心开发人员与C ++之间的联系   提升lib,我会假设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 */

weak

我们可以在Swift运行时swift/include/swift/Runtime/HeapObject.h specifically中查看/// 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); (Swift)引用的基本处理:

weak

这里的关键是评论

  

如果当前值是已开始取消分配的非null对象,   返回null;否则,在返回前保留对象

由于这是基于后端运行时代码注释,它仍然有点推测,但我会说上面暗示当试图访问weak引用指向的值时,确实会引用在通话期间保留为强者(&#34; ......直到返回&#34; )。

为了尝试从上面兑换&#34;有些推测性的&#34; 部分,我们可能会继续深入研究Swift如何通过swift_weakLoadStrong(...)引用来处理值的访问。从@idmean:s comment below(研究生成的SIL代码,例如OP:s)我们知道调用swift_weakLoadStrong(...)函数。

因此,我们首先考察swift/stdlib/public/runtime/HeapObject.cppHeapObject *swift::swift_weakLoadStrong(WeakReference *ref) { return ref->nativeLoadStrong(); } 函数的实现情况,并了解我们从哪里获取:

nativeLoadStrong()

我们从swift/include/swift/Runtime/HeapObject.h

中找到了WeakReference HeapObject *nativeLoadStrong() { auto bits = nativeValue.load(std::memory_order_relaxed); return nativeLoadStrongFromBits(bits); } 方法的实现
nativeLoadStrongFromBits(...)

the same file开始,HeapObject *nativeLoadStrongFromBits(WeakReferenceBits bits) { auto side = bits.getNativeOrNull(); return side ? side->tryRetain() : nullptr; } 的实施:

tryRetain()

继续调用链,HeapObjectSideTableEntryHeapObject* tryRetain() { if (refCounts.tryIncrement()) return object.load(std::memory_order_relaxed); else return nullptr; } 的方法(对the object lifecycle state machine来说很重要),我们在swift/stdlib/public/SwiftShims/RefCount.h

中找到它的实现方式
tryIncrement()

可以找到RefCounts类型的// Increment the reference count, unless the object is deiniting. bool tryIncrement() { ... } 方法(此处通过typedef:ed specialization of it的实例调用)的实现in the same file as above

lhs

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

答案 2 :(得分:3)

  

这总是安全吗

没有。你没有做过强弱的舞蹈&#34;。做吧!每当你使用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 ...
    }
})

答案 3 :(得分:2)

文档明确states,如果确定作业的左侧为零,则不会评估右侧。 但是,在给定的示例中,self弱引用,并且可能在可选检查通过后立即释放(并且无效),但是在强制解包之前,将使整个表达式为零-unsafe。

答案 4 :(得分:-4)

在更正之前:

我认为其他人已经尽可能地回答了你问题的细节。

但除了学习。如果您确实希望您的代码可靠地运行,那么最好这样做:

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

阅读这篇伟大的post on closure capturing。每当你在括号[]中看到某些内容时,我都会盲目地想到它意味着它被捕获了,它的价值并没有改变。但我们在括号中做的唯一事情就是我们weak - 如果它正在使我们知道它的价值可以成为{{1} }}。如果我们做了类似nil之类的事情,我们就会成功捕获它,但是我们仍然会遇到一个问题,即持有一个指向[x = self]本身的强指针并创建一个内存周期。 (从某种意义上来说,这很有意思,因为你因为你的弱点而被释放,因此从创建内存周期到创建崩溃是一条很细的路线。)

总结如下:

  1. self

    创建记忆周期。不好!<​​/ p>

  2. [capturedSelf = self]

    (如果你事后强行[weak self] in guard let _ = self else{ return } 打开会导致崩溃) self它是完全无用的。因为下一行,guard let仍然可以成为self。不好!<​​/ p>

  3. nil

    (如果您强制[weak self] self?.method1() 解包后可能会导致崩溃。如果self不是self,则可能会失败。如果nil为{{1},则会安全失败这是你最想要的东西。这是

  4. self

    如果nil已取消分配,则会安全失败;如果不是[weak self] in guard let strongSelf = self else{ return } ,则会继续。但它有点挫败了目的,因为当你删除它自己的引用时,你不应该与self进行通信。我无法想到一个很好的用例。这可能没用!