web-dev-qa-db-ja.com

同じコード行でオプションでアクセスされた変数を強制的にアンラップしても安全ですか?

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

これは常に安全ですか?私はステートメントの冒頭でオプションのselfにアクセスします。個人的には、selfnilの場合、このステートメントの2番目の部分は実行されないと想定しています。これは本当ですか? selfが実際にnilの場合、2番目の部分は決して発生しませんか?そして、この1行のコードの実行中にselfが 'nilled'になる可能性は決してありませんか?

38
Sti

オプションの連鎖 「Swiftプログラミング言語」からの例は、次の例を示しています。

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

続いて(強調を追加):

この例では、john.residenceが現在nilであるため、john.residenceのアドレスプロパティを設定しようとしても失敗します。

代入はオプションの連鎖の一部です。つまり、=演算子の右側のコードは評価されません

あなたのケースに適用:

self?.variable = self!.otherVariable

selfnilの場合、右側はnotと評価されます。したがって、あなたの質問への答え

自己が本当にnilの場合、2番目の部分は決して発生しませんか?

「はい」です。 2番目の質問に関して

そして、この1行のコードの実行中に、自己が「埋もれる」ことは決してないでしょうか。

私の元の仮定は、self!= nilであると判断されると、self!への強い参照が、これは起こり得ないように、ステートメント。ただし(@Hamishが指摘したように)、これは保証されません。 AppleエンジニアJoe Groffが Confirming order of operations Swiftフォーラムで書いています:

これは保証されていません。リリースは、これよりも前に、最後に強参照が正式に使用された後の任意の時点で最適化される場合があります。左側[weakProperty?.variable]を評価するためにロードされた強参照は後で使用されないため、有効な状態を維持するものがないため、すぐに解放できます。
weakPropertyによって参照されるオブジェクトの割り当てが解除される原因となる変数のゲッターに副作用がある場合、弱い参照を削除してから、これにより、右側の強制アンラップが失敗します。ifを使用して弱い参照をテストし、if letによってバインドされた強い参照を参照する必要があります

25
Martin R

いいえ、これは安全ではありません

以下のコメントで@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!)
    }
}
_

上記の2つのケースで、Swiftによって、weakPropertyをこれらの位置で強制的にラップ解除できることが保証されますか?

保証について興味がありますSwiftは、オプションのチェーン中にアクセスについて行うたとえば、_weakProperty!_アクセサーは、オプションのチェーンが最初に値が既にnilでないと判断した場合にのみ起動することが保証されています?

さらに、この評価の期間中、弱いオブジェクトは保持されることが保証されますか、または弱い変数は、オプションのアクセスと呼び出されるメソッドの間で割り当てを解除できる可能性がありますか?

Joe_Groff:

これは保証されていません。リリースは、これよりも前に、最後に強参照が正式に使用された後の任意の時点で最適化される場合があります。 左側(weakProperty?.variable_)を評価するためにロードされた強参照は後で使用されないため、それを維持するものは何もないため、すぐに解放できます。変数のゲッターに副作用があり、weakPropertyによって参照されるオブジェクトの割り当てが解除される場合、nil-弱い参照を除外すると、右側で強制的にアンラップすると失敗します。 if letを使用して弱参照をテストし、if letによってバインドされた強参照を参照する必要があります。

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

弱参照は4回ではなく1回読み込まれてテストされるため、これはより安全で効率的です。


上記のJoe Groffの回答を引用すると、私の以前の回答は意味がありませんが、Swiftコンパイラーの深部へのおそらく興味深い(失敗したとしても)旅としてここに残します。


歴史的な答えは正しくない最終的な議論に到達しましたが、それでも興味深い旅を通して、

この回答は、@ appzYourLife:s削除済みの回答に対するコメントに基づいて作成します。

これは純粋な推測ですが、経験豊富なSwiftコア開発者とC++:s Boost libの間のいくぶん緊密な関係を考えると、weak参照は有効期間中、強い参照にロックされていると思いますこれは、C++の対応する関数の明示的に使用される std::weak_ptr::lock() と同様に、selfで何かを割り当て/変更する場合の式の.

selfweak参照によってキャプチャされ、割り当て式の左側にアクセスするときにnilではない例を見てみましょう。

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

Swift runtime、 _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以外のオブジェクトの場合、nullを返します。それ以外の場合、は戻る前にオブジェクトを保持します

これはバックエンドランタイムコードのコメントに基づいているため、まだいくらか投機的ですが、上記のことは、weak参照が指す値にアクセスしようとすると、実際に参照が存続期間中強力なものとして保持されることを意味します呼び出しの( "...戻るまで")。


上記の「やや投機的」の部分を引き換えようとするために、Swiftがweakを介して値のアクセスを処理する方法を掘り下げますリファレンス @ idmean:s​​のコメントの下 (OP:sのような例の生成されたSILコードを調べる)から、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_ から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() {
  ...
}
_

ここでのコメントは、このメソッドをエンドポイントとして使用するのに十分であると考えています。オブジェクトがdeinitingでない場合(上で想定しているように、そうではありません。OP:sの例のlhsの代入は、成功)、オブジェクトの(強力な)参照カウントが増加し、HeapObjectポインター(強い参照カウントの増分に基づく)が代入演算子に渡されます。対応する参照カウントの減分が最終的に割り当ての最後に実行される方法を調べる必要はありませんが、weak参照に関連付けられたオブジェクトは、割り当ての存続期間中、強力なオブジェクトとして保持されることが推測を超えて、 @ MartinR:s answer で説明されているように、左側へのアクセス時に解放/割り当て解除されていません(この場合、右側は処理されません)。 )。

16
dfri

これは常に安全ですか

いいえ。あなたは「弱いダンス」をしていません。やれ! 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 ...
    }
})
3
matt

ドキュメントは明らかに states であり、割り当ての左側がnilであると判断された場合、右側は評価されません。ただし、指定された例では、selfweak referenceであり、オプションのチェックに合格した直後に解放(および無効化)できますが、 force-unwrapが発生する前に、式全体をnil-unsafeにします。

2
Gleb Lukianets