🧵
クロージャのキャプチャリストの処理はクロージャ生成時に生成元のスレッドで実行されることがわかりやすく表現できるサンプル
概要
ロージャのキャプチャリストの代入は、クロージャの生成時に生成元のスレッドで実行されることがわかりやすく表現できるサンプルできたので残しておきます。
試した環境
- Xcode 14.3.1
- Playground
サンプル
同期処理
まずは同期処理で考えた方がシンプル。
struct Piyo {
static func make() -> Piyo {
print("Piyo:", #function, "isMainThread?", Thread.isMainThread)
return .init()
}
}
// MARK: -
print("begin isMainThread?", Thread.isMainThread)
let closure = { [piyo = Piyo.make()] in
print("closure:", piyo, "isMainThread?", Thread.isMainThread)
}
print("closure()")
closure()
print("end")
結果は次のとおり
begin isMainThread? true
Piyo: make() isMainThread? true
closure()
closure: Piyo() isMainThread? true
end
closure()
関数の実行前、
let closure
を作成時にPiyoのmake
メソッドが実行されているのがわかる。
すべてメインスレッドで実行しているのもisMainThread()
関数で念のため確認。
非同期処理
同期処理のクロージャの例がわかりやすいが、身近な話に近づけていくためにクロージャを即時実行される非同期処理に置き換えてみる。
そうなるとついでにweakにも触れる。
class Hoge {
static func make() -> Hoge {
print("Hoge:", #function, "isMainThread?", Thread.isMainThread)
return .init()
}
deinit {
print("Hoge:", #function)
}
}
struct Piyo {
static func make() -> Piyo {
print("Piyo:", #function, "isMainThread?", Thread.isMainThread)
return .init()
}
}
print("begin isMainThread?", Thread.isMainThread)
// クロージャの作成時にキャプチャリストの処理が実行されるはず
DispatchQueue.global().async { [weak hoge = Hoge.make(), piyo = Piyo.make()] in
// クロージャ作成時に生成されたhogeはweakとなり、
// 強参照の参照カウントが上がらないため、
// クロージャを実行時にはすでに解放されている。
print("closure:", hoge, piyo, "isMainThread?", Thread.isMainThread)
// さらにhoge, piyoインスタンスはクロージャ生成したメインスレッドで生成されたが、
// このクロージャの別スレッドで利用されることになりスレッドを越えている。
}
print("end")
出力結果は次のとおり
begin isMainThread? true
Hoge: make() isMainThread? true
Hoge: deinit
Piyo: make() isMainThread? true
end
closure: nil Piyo() isMainThread? false
言いたいことが2つになってしまったが
- Hogeクラスのインスタンスは
weak
によって参照カウントが上げられていないので即時deinit
- そのタイミングはクロージャの作成時だとわかる(同期処理の例の方がわかりやすいけど)
-
DispatchQueue.global().async
のクロージャはメインスレッドでははない- Hogeの
make
はクロージャ生成がメインスレッドなのでメインスレッドでクロージャ作成時に実行される
- Hogeの
weakの話はとりあえずわかっていると思うのでそっとしておいて、ここで伝えたいのはキャプチャリストに入れようが外部から作成したインスタンスを非同期処理のクロージャに入れるということは、スレッドをまたがってしまうということだ。
言い換えると、キャプチャリストに入れても境界を越えてくるので、いい感じにSendable化しないといけない。
Task
今更DispatchQueueを使いたいわけじゃないので、Taskの例を示しておく。
print("begin isMainThread?", Thread.isMainThread)
Task { @MainActor in
print("MainActor:")
Task.detached { [weak hoge = Hoge.make(), piyo = Piyo.make()] in
print("closure:", hoge, piyo, "isMainThread?", Thread.isMainThread)
}
}
print("end")
結果は次のとおり
begin isMainThread? true
end
MainActor:
Hoge: make() isMainThread? true
Hoge: deinit
Piyo: make() isMainThread? true
closure: nil Piyo() isMainThread? false
MainActorを即時実行できていないことから、PlaygroundのTop-Levelの実行はMainActorではないがメインスレッドなんだろうか、という謎の余韻を残してしまってはいる。
Discussion