【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