
ジョセフ・ライト、「囚人」 -「強い」捕獲の実例
「キャプチャされた」値のリストは、クロージャパラメータのリストの前にあり、「strong」、「weak」または「unowned」のリンクを使用して、スコープから値を「キャプチャ」できます。 主に、強い参照サイクル(「強い参照サイクル」、別名「保持サイクル」)を避けるためによく使用します。
初心者の開発者が適用する方法を決定するのは困難な場合があるため、「強い」と「弱い」、または「弱い」と「未所有」を選択するのに多くの時間を費やすことができますが、時間の経過とともに、正しい選択-1つだけ。
まず、単純なクラスを作成します。
class Singer { func playSong() { print("Shake it off!") } }
次に、 Singerクラスのインスタンスを作成し、 SingerクラスのplaySong()メソッドを呼び出すクロージャーを返す関数を作成します。
func sing() -> () -> Void { let taylor = Singer() let singing = { taylor.playSong() return } return singing }
最後に、どこでもsing()を呼び出してplaySong()を再生した結果を取得できます
let singFunction = sing() singFunction()
その結果、「Shake it off!」という文字列が表示されます。
強力なキャプチャ
キャプチャ方法を明示的に指定しない限り、Swiftは「強力な」キャプチャを使用します。 これは、クロージャが使用される外部値をキャプチャし、それらを解放しないことを意味します。
もう一度sing()関数を見てみましょう
func sing() -> () -> Void { let taylor = Singer() let singing = { taylor.playSong() return } return singing }
テイラー定数は関数内で定義されるため、通常の状況では、関数が処理を完了するとその場所は解放されます。 ただし、この定数はクロージャー内で使用されます。つまり、Swiftは、クロージャー自体が存在する限り、関数の終了後もその存在を自動的に保証します。
これは、実行中の「強力な」キャプチャです。 Swiftがtaylorの解放を許可した場合、クロージャの呼び出しは安全ではなくなります-taylor.playSong()メソッドは無効になります。
「弱い」キャプチャ(弱いキャプチャ)
Swiftでは、「 キャプチャリスト 」を作成して、使用される値のキャプチャ方法を決定できます。 「強力な」キャプチャの代替手段は「弱」であり、そのアプリケーションは次の結果をもたらします。
1.「弱く」キャプチャされた値はクロージャによって保持されないため、解放してnilに設定できます。
2.最初の段落の結果として、Swiftで「弱く」キャプチャされた値は常にオプションです 。
「弱い」キャプチャを使用して例を変更し、すぐに違いを確認します。
func sing() -> () -> Void { let taylor = Singer() let singing = { [weak taylor] in taylor?.playSong() return } return singing }
[弱いテイラー] -これは、「 キャプチャリスト 」です。これは、値をキャプチャする方法を指示するクロージャ構文の特別な部分です。 ここでは、 テイラーを「弱く」キャプチャする必要があると言うので、 テイラーを使用する必要がありますか?.PlaySong() -いつでもnilに設定できるため、 オプションになりました。
このコードを実行すると、 singFunction()を呼び出してもメッセージが表示されなくなることがわかります。 これは、 テイラーがsing()の内部にのみ存在し、この関数によって返されるクロージャーがテイラーを内部に「強く」保持しないためです。
テイラーを変更してみてください?.PlaySong() to taylor!.PlaySong() 。 これにより、クロージャ内でテイラーが強制的に解凍され、それに応じて致命的なエラーが発生します( nilを含むコンテンツの解凍)
「所有者なし」のキャプチャ(所有されていないキャプチャ)
「弱い」キャプチャの代替手段は「所有者なし」です。
func sing() -> () -> Void { let taylor = Singer() let singing = { [unowned taylor] in taylor.playSong() return } return singing }
このコードは、上に示したオプションでデプロイされたオプションと同様の方法で異常終了します。所有していないテイラーは、「クロージャーがアクティブな間は常にテイラーが存在することを知っているので、記憶に留める必要はありません。」 実際、 テイラーはほぼすぐにリリースされ、このコードはクラッシュします。
したがって、非所有者は非常に慎重に使用してください。
一般的な問題
開発者がクロージャで値のキャプチャを使用するときに直面する4つの問題があります。
1.クロージャーがパラメーターを取るときのキャプチャリストの場所に関する問題
これは、クロージャの調査の開始時に遭遇する一般的な困難ですが、幸いなことに、この場合はSwiftが役立ちます。
キャプチャリストとクロージャパラメータを一緒に使用する場合、キャプチャリストは最初に角括弧で囲まれ、次にクロージャパラメータ、次にinキーワードでクロージャ「body」の始まりをマークします。
writeToLog { [weak self] user, message in self?.addToLog("\(user) triggered event: \(message)") }
クロージャパラメータの後にキャプチャリストを配置しようとすると、コンパイルエラーが発生します。
2.メモリリークにつながる強力なリンクのサイクルの出現
エンティティAにエンティティBがあり、その逆の場合、「保持サイクル」と呼ばれる状況があります。
例として、コードを考えてみましょう:
class House { var ownerDetails: (() -> Void)? func printDetails() { print("This is a great house.") } deinit { print("I'm being demolished!") } }
1つのプロパティ(クロージャ)、1つのメソッド、およびクラスインスタンスが破棄されたときにメッセージを表示するデイニシャライザを含むHouseクラスを定義しました。
次に、クロージャープロパティに家に関する情報が含まれていることを除いて、前のクラスと同様のOwnerクラスを作成します。
class Owner { var houseDetails: (() -> Void)? func printDetails() { print("I own a house.") } deinit { print("I'm dying!") } }
次に、 doブロック内にこれらのクラスのインスタンスを作成します。 catchブロックは必要ありませんが、 doブロックを使用するとすぐにインスタンスが破壊されます}
print("Creating a house and an owner") do { let house = House() let owner = Owner() } print("Done")
その結果、「家と所有者を作成しています」、「死にかけています!」、「破壊されています!」、「完了」というメッセージが表示されます。すべて正常に機能します。
次に、強力なリンクのループを作成します。
print("Creating a house and an owner") do { let house = House() let owner = Owner() house.ownerDetails = owner.printDetails owner.houseDetails = house.printDetails } print("Done")
「家と所有者を作成しています」というメッセージが表示され、次に「完了」が表示されます。 デイニシャライザーは呼び出されません。
これは、家には所有者を指すプロパティがあり、所有者には家を指すプロパティがあるという事実の結果として発生しました。 したがって、それらのどれも安全にリリースできません。 実際の状況では、これによりメモリリークが発生し、パフォーマンスが低下したり、アプリケーションがクラッシュしたりします。
状況を修正するには、新しいクロージャーを作成し、次のように1つまたは2つのケースで「弱い」キャプチャを使用する必要があります。
print("Creating a house and an owner") do { let house = House() let owner = Owner() house.ownerDetails = { [weak owner] in owner?.printDetails() } owner.houseDetails = { [weak house] in house?.printDetails() } } print("Done")
キャプチャされた両方の値を宣言する必要はありません。1か所で行うだけで十分です。これにより、必要に応じてSwiftが両方のクラスを破棄できます。
実際のプロジェクトでは、このような強いリンクの明らかなサイクルの状況はめったに発生しませんが、これは有能な開発で「弱い」キャプチャを使用することの重要性をさらに語っています。
3.通常、複数の値をキャプチャする際に、誤って強力なリンクを使用する
Swiftはデフォルトで強力なグリップを使用するため、予期しない動作が発生する可能性があります。
次のコードを検討してください。
func sing() -> () -> Void { let taylor = Singer() let adele = Singer() let singing = { [unowned taylor, adele] in taylor.playSong() adele.playSong() return } return singing }
これでクロージャーによってキャプチャされた2つの値があり、両方を同じ方法で使用します。 ただし、 テイラーのみが未所有としてキャプチャされます。キャプチャされた各値に未所有キーワードを使用する必要があるため、 アデルが強くキャプチャされます。
意図的にこれを行った場合はすべて問題ありませんが、両方の値を「 未所有 」にキャプチャする場合は、次のものが必要です。
[unowned taylor, unowned adele]
4.クロージャーをコピーし、キャプチャした値を共有します
開発者がつまずく最後のケースは、彼らがキャプチャしたデータが障害のすべてのコピーで利用可能になるため、障害がどのようにコピーされるかです。
クロージャーの外部で宣言された整数変数numberOfLinesLoggedをキャプチャする単純なクロージャーの例を考えてみましょう。これにより、クロージャーが呼び出されるたびに値を増やして出力できます。
var numberOfLinesLogged = 0 let logger1 = { numberOfLinesLogged += 1 print("Lines logged: \(numberOfLinesLogged)") } logger1()
これにより、「Lines logging:1」というメッセージが表示されます。
ここで、キャプチャの値を最初のクロージャとともに共有するクロージャのコピーを作成します。 したがって、元のクロージャーまたはそのコピーが発生し、変数の値が増加するのがわかります。
let logger2 = logger1 logger2() logger1() logger2()
logger1とlogger2が同じキャプチャされたnumberOfLinesLogged変数を指しているため、これにより、メッセージ「Lines logsed :1」...「Lines logsed :4」が出力されます。
「強力な」キャプチャ、「弱」、「無所有者」を使用する場合
すべてがどのように機能するかを理解したので、要約してみましょう。
1.クロージャを実行するときにキャプチャされた値が決してnilにならないことが確実な場合は、 「unowned capture」を使用できます。 これは、ガードレットを使用してクロージャー内の弱くキャプチャされた値を使用する場合でも、「弱い」キャプチャを使用すると追加の問題が発生する可能性がある場合はまれです。
2.強いリンクのサイクルのケースがある場合(エンティティAがエンティティBを所有し、エンティティBがエンティティAを所有している場合)、いずれかのケースで「弱いキャプチャ」を使用する必要があります。 2つのエンティティのどちらを最初に解放するかを考慮する必要があるため、View Controller AがView Controller Bを表す場合、View Controller Bには「A」に戻る「weak」リンクが含まれる場合があります。
3.強力なリンクのサイクルの可能性が除外されている場合、「強力な」キャプチャ( 「強力なキャプチャ」 )を使用できます。 たとえば、アニメーションを実行しても、アニメーションを含むクロージャ内の自己はブロックされないため、強力なバインディングを使用できます。
4.わからない場合は、「弱い」バインディングから始めて、必要な場合にのみ変更します。
オプション-公式Swiftガイド:
短絡
自動リンクカウント