【Swift Concurrency】sendingクロージャについての軽く調べてみた
調査:sendingクロージャについて
sendingクロージャは、クロージャ内で参照しているnon-Sendableな値をsendingされたものとして扱うクロージャ。
より詳しく言うと、「クロージャがnon-Sendableな値をキャプチャした時に、その値がキャプチャ後に使用されないこと」を保証させる。
個人的にsendingクロージャは、@Sendableクロージャが安全にnon-Sendableな値をキャプチャできるようになったものだと認識。
@Sendableクロージャは、クロージャ内でnon-Sendableな値をキャプチャすることができない。
class NonSendableClass {
var num = 0
}
func action1(sendableClosure: @Sendable () -> Void) {
// 何らかの処理
}
func action2() {
let nonSendableClass = NonSendableClass()
action1(sendableClosure: {
print(nonSendableClass) // ※1
})
}
// ※1: Capture of 'nonSendableClass' with non-sendable type 'NonSendableClass' in a `@Sendable` closure
上記の例では、non-SendableClassをキャプチャしても特に不具合が起こることはないが、@Sendableクロージャ内はnon-Sendableな値をキャプチャできないためエラーが出る。
action1関数を@Sendableクロージャからsendingクロージャに変更してみる。
class NonSendableClass {
var num = 0
}
func action1(sendingClosure: sending () -> Void) {
// 何らかの処理
}
func action2() {
let nonSendableClass = NonSendableClass()
action1(sendingClosure: {
print(nonSendableClass)
})
}
先ほどの@Sendableクロージャと違い、sendingクロージャではnon-Sendableな値をキャプチャしても問題はない。
おそらくではあるが、sendingクロージャ内でnon-Sendableな値をキャプチャした場合、その値はsendingしたものとみなされるためだと思われる。
検証として以下のコードのようにaction1関数の実行後に、non-SendableClassにアクセスするとエラーが出る。
class NonSendableClass {
var num = 0
}
func action1(sendingClosure: sending () -> Void) {
// 何らかの処理
}
func action2() {
let nonSendableClass = NonSendableClass()
action1(sendingClosure: { // ※1
print(nonSendableClass)
})
print(nonSendableClass)
}
// ※1: Value of non-Sendable type '@noescape @callee_guaranteed () -> ()' accessed after being transferred; later accesses could race
sendingクロージャについての調査は以上になります。他に何か分かったことがあれば追記しようと思います。
追記:変数のキャプチャについて
sendingクロージャは変数のキャプチャ可能。
func action1(sendingClosure: sending () -> Void) {
// 何らかの処理
}
func action2() {
var num = 0
action1(sendingClosure: {
print(num)
})
num += 1
print(num)
}
@Sendingクロージャは変数のキャプチャ不可。
func action1(sendableClosure: @Sendable () -> Void) {
// 何らかの処理
}
func action2() {
var num = 0
action1(sendableClosure: {
print(num) // ※1
})
num += 1
print(num)
}
// ※1: Reference to captured var 'num' in concurrently-executing code
蛇足:Task関連のoperationについて
TaskのイニシャライザやTaskGroup等のaddTaskのoperationが@Sendableなクロージャからsendingなクロージャに変更されています。以下はTask.init(operation:)の変更前と変更後です。
[変更前]
@discardableResult
public init(priority: TaskPriority? = nil, operation: @escaping @Sendable () async throws -> Success)
[変更後]
@discardableResult
public init(priority: TaskPriority? = nil, operation: sending @escaping @isolated(any) () async -> Success)
Discussion