Sponsor Link
環境&対象
- macOS Ventura 13.4 Beta
- Xcode 14.3 RC
- iOS 16.4 beta
Task
Swift Concurrency の一部としてリリースされました。
Apple のドキュメントは、こちら。
使用中のスレッドではなく、別のスレッドを使用するように処理でき、現在のスレッドをブロックしないようにできます。
別スレッドで処理するようにした Task の処理が終わるまで、(現スレッドを) suspend して待つこともできますし、
またずに、別の処理を進めることも可能です。
# Task に渡した closure の実行自体は、いつ行われるかは不明です。
# このあたりは、Google 検索すると解説記事が多く出てくると思います。
Task の返り値
あまり言及されることがないのですが、Task は、返り値を持ちます。
func myFunc() {
Task {
print("Hello")
}
}
上記の myFunc というメソッドは、実行されると 内部で Task を生成し実行させています。
生成された Task では、指定された closure (print 文だけです) が実行されます。
上記のコードを省略(?) せずに書くと以下のようになります。
func myFunc() {
let myTask = Task<Void,Never> {
print("Hello")
}
}
このときの型は Task<Void,Never> です。
# Task のインスタンスは、Task の実行をキャンセルするときにも必要になりますので、返り値を変数に保持するコードを見たことはあるのではないでしょうか。
Task は、2つの type parameter を持ちます。1つめの Void は、closure の返り値を表しています。現在のコードは、print するだけで 値を返さないので、Void です。
2つめの Never は、closure が例外を送出しないことを表しています。
closure が値を返すようにしてみます。以下のようにコンパイルエラーが発生します。
func myFunc() {
let myTask = Task<Void,Never> { // - compile error: Cannot convert value of type 'Int' to closure result type 'Void'
print("Hello")
return 1
}
}
エラーを解消するには、Task<Int,Never> と変更する必要があります。
func myFunc() {
let myTask = Task<Int,Never> {
print("Hello")
return 1
}
}
同様に、closure から 例外を送出してみます。
やはり、コンパイルエラーが発生します。
func myFunc() {
let myTask = Task<Void,Never> { // - compile error: No exact matches in call to initializer
print("Hello")
throw MyError.justError
}
}
エラーメッセージが少しわかりにくいですが、throw するような closure を持つ時に該当する initializer が存在しないという意味です。
先ほどと同様に、Task の type parameter を修正するとコンパイルエラーが解消されます。
let myTask = Task<Void,Error> {
print("Hello")
throw MyError.justError
}
このとき、Error を具体的な Error (例えば、MyError) にしたくなりますが、できません。throw するものは、Error としか定義されていないので、Task でも Error での定義しかできません。
# Task が独自に定義/利用するのではなく、closure の throw するものを渡してくれるものなので、throw の振る舞いに従っています。
エラー処理
準備ができた(?) ので、エラー処理を考えてみます。
Task 内で例外を処理, 返り値で反映
Task 内で例外を処理することは、これまでと同じです。
let task = Task<String,Never> {
do {
try throwableTask()
} catch {
// handle error
return "error"
}
return "done"
}
例外を catch したときに return “error” することに議論の余地はありますが、Task の中で、 例外を処理することに 特殊な考慮は必要ありません。
# 内部で例外を catch しているので、Task<String, “Never”> になっていることに気をつけてください。
このような処理をしたときに、呼び出し側は、以下のようになっているはずです。
func myFunc() {
let task = Task<String,Never> { // (1) Task を保持
do {
try throwableTask()
} catch {
// handle error
return "error"
}
return "done"
}
Task { // (2) 別 Task を使用
switch await task.value { // (3) Task 結果をチェック
case "error":
print("error occured") // (4) エラー処理
default:
print("result: \(await task.value)")
}
}
}
- Task を生成、保持
- 呼び出し側は、sync なので、結果を確認するために 別 Task を生成
- task.value に保存される 結果(この場合 String) をチェック。async な プロパティなので、await を使ったアクセスが必要です
- 保持されている値によって エラー処理
value を使った エラー処理を推奨しているわけではありません。あくまで、このような渡し方も技術的に可能という意味です。
例外を Task 外部に伝播してエラー処理
上記の例は、例外を 無理やり(?) 返り値に変換しています。
Task 内部で発生した例外を Task 外部で受け取る方法ももちろんあります。
func myFunc() {
let task = Task<String,Error> {
try throwableTask()
return "done"
}
Task {
switch await task.result { // (1)
case .success(let value): // (2)
print("Result: \(value)")
case .failure(let error): // (3)
if let myError = error as? MyError {
print("error occured")
} else {
// unknown error?
}
}
}
前半の Task 生成は同じなので、2つ目の Task を説明します。
- task.result に結果と例外情報が保持されています。value 同様 async な プロパティなので、await を使ったアクセスが必要です
- result には、Result 形で保持されていて、.success には、value と同様に 結果が保持されています
- .failure に Error が保持されています。上記では 型チェックを行なってエラー処理をしています。
このようにすると、Task 内部で発生した例外を Task 外部でもチェックすることが可能となります。
ただし、Task の結果には async なアクセスしかできませんので、チェックする側が async であるか、別 Task を使ってのチェックが必要となります。
まとめ
Task から発生する例外の処理方法を確認してみました。
- Task の返り値 value をチェックすると closure の返り値がわかる
- result をチェックすると closure の返り値、closure の throw した Error の両方がわかる
- value/result には async なアクセスしかできない
説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。
SwiftUI おすすめ本
SwiftUI を理解するには、以下の本がおすすめです。
SwiftUI ViewMatery
SwiftUI で開発していくときに、ViewやLayoutのための適切なmodifierを探すのが大変です。
英語での説明になってしまいますが、以下の”SwiftUI Views Mastery Bundle”という本がビジュアル的に確認して探せるので、便利です。
英語ではありますが、1ページに コードと画面が並んでいるので、非常にわかりやすいです。
View に適用できる modifier もわかりやすく説明されているので、ビューの理解だけではなく、どのような装飾ができるかも簡単にわかります。
超便利です
販売元のページは、こちらです。
SwiftUI 徹底入門
# SwiftUI は、毎年大きく改善されていますので、少し古くなってしまいましたが、いまでも 定番本です。
Swift学習におすすめの本
詳解Swift
Swift の学習には、詳解 Swift という書籍が、おすすめです。
著者は、Swift の初期から書籍を出していますし、Swift の前に主力言語だった Objective-C という言語についても同様の書籍を出しています。
最新版を購入するのがおすすめです。
現時点では、上記の Swift 5 に対応した第5版が最新版です。
Sponsor Link