今日現在、以下の設定でやっています。
【Swift】Strict Concurrency Checking の設定 https://t.co/wmXyJ8ipOv #Qiita
— chanzmao (@maochanz) June 25, 2024
お天気情報を取得する
無料で公開されている WEB API を使います。
シンプルに取得する。
import SwiftUI
struct TestWeatherView: View {
private let url = URL(string: "https://wttr.in/?format=3")!
var body: some View {
Text(try! String(contentsOf: url))
}
}
#Preview {
TestWeatherView()
}
ちょっと窮屈なので砕いていきます。
struct TestWeatherView: View {
@State private var text = ""
private let url = URL(string: "https://wttr.in/?format=3")!
var body: some View {
Text(text)
.onAppear {
text = try! String(contentsOf: url)
}
}
}
OK です。
次は、Web クライアントを汎用性のある URLSession
に変えます。
struct TestWeatherView: View {
@State private var text = ""
private let url = URL(string: "https://wttr.in/?format=3")!
var body: some View {
Text(text)
.task {
let (data, _) = try! await URLSession.shared.data(from: url)
text = String(data: data, encoding: .utf8)!
}
}
}
ここで、警告がでます。
Passing argument of non-sendable type '(any URLSessionTaskDelegate)?' outside of main actor-isolated context may introduce data races
これは、なんですか。
Passing argument of non-sendable type '(any URLSessionTaskDelegate)?' outside of main actor-isolated context may introduce data races
URLSession
のドキュメントを見ておきます。
func data(from url: URL) async throws -> (Data, URLResponse)
data(from:) | Apple Developer Documentation
Task
内の以下の部分
let (data, _) = try! await URLSession.shared.data(from: url)
左辺はメインスレッド、
--- 境界 ---
右辺はバックグラウンドスレッド
【Swift】concurrency をマスターするための一つのきっかけ
ということで、
いわゆる「アクター境界」を越えているので、
data()
の返り値は Sendable
でなければなりません。
リファレンスやコードを追いかけてみると、
Data
, URLResponse
共に Sendable
に準拠しています。
Data | Apple Developer Documentation
URLResponse | Apple Developer Documentation
あ、Tuple
かな ?
などと思いましたがなんか違う。
Pitch: User-defined tuple conformances - Evolution / Pitches - Swift Forums
Actor を使う
Actor
は Sendable
に準拠しています。
【Swift】Actor は 元から Sendable に conform している件
https://t.co/2cWTWoryDK #Swift #プログラミング— chanzmao (@maochanz) July 9, 2024
なので、これでバックグラウンドの通信部分をラップします。
actor WeatherA {
// OK
static func getData(url: URL) async -> (Data, URLResponse) {
try! await URLSession.shared.data(from: url)
}
// OK
nonisolated func getData(url: URL) async -> (Data, URLResponse) {
try! await URLSession.shared.data(from: url)
}
}
これで、無警告でOKとなりました。
extension 化してメインスレッドを避ける
こんな方法でもいけます。
extension URLSession {
func dataEx(url: URL) async -> (Data, URLResponse) {
try! await data(from: url)
}
}
まとめ
分かれば、なるほど感あるけど、分からなければ全く謎で時間だけ食うので、
そんな誰かと自分用のメモとして。
class でも警告なしでいけるようです。
import SwiftUI | |
struct TestView: View { | |
@State private var text = "-" | |
private let url = URL(string: "https://wttr.in/?format=3")! | |
//private let url = URL(string: "https://httpbin.org/get")! | |
var body: some View { | |
Text(text) | |
.task { | |
// ! Passing argument of non-sendable type '(any URLSessionTaskDelegate)?' | |
// outside of main actor-isolated context may introduce data races | |
//let (data, _) = try! await URLSession.shared.data(from: url) | |
// ! Capture of 'self' with non-sendable type 'TestView' in 'async let' binding | |
// Consider making struct 'TestView' conform to the 'Sendable' protocol | |
//async let (dataEx, _) = try! await URLSession.shared.data(from: url) | |
//let data = await dataEx | |
// ! Passing argument of non-sendable type 'TestView' | |
// outside of main actor-isolated context may introduce data races | |
// Consider making struct 'TestView' conform to the 'Sendable' protocol | |
//let (data, _) = await dataEx(url: url) | |
// OK | |
//let (data, _) = await URLSession.shared.dataEx(url: url) | |
// OK | |
//let (data, _) = await url.dataEx() | |
// OK | |
//let (data, _) = await WeatherA.getData(url: url) | |
// OK | |
//let (data, _) = await WeatherA().getData(url: url) | |
// OK | |
//let (data, _) = await WeatherC.getData(url: url) | |
// OK | |
let (data, _) = await WeatherC().getData(url: url) | |
text = String(data: data, encoding: .utf8)! | |
} | |
} | |
// ! | |
nonisolated private func dataEx(url: URL) async -> (Data, URLResponse) { | |
try! await URLSession.shared.data(from: url) | |
} | |
} | |
// OK | |
extension URLSession { | |
func dataEx(url: URL) async -> (Data, URLResponse) { | |
try! await data(from: url) | |
} | |
} | |
// OK | |
extension URL { | |
func dataEx() async -> (Data, URLResponse) { | |
try! await URLSession.shared.data(from: self) | |
} | |
} | |
actor WeatherA { | |
// OK | |
static func getData(url: URL) async -> (Data, URLResponse) { | |
try! await URLSession.shared.data(from: url) | |
} | |
// OK | |
nonisolated func getData(url: URL) async -> (Data, URLResponse) { | |
try! await URLSession.shared.data(from: url) | |
} | |
} | |
final class WeatherC: Sendable { | |
//class WeatherC: @unchecked Sendable { | |
// OK | |
static func getData(url: URL) async -> (Data, URLResponse) { | |
try! await URLSession.shared.data(from: url) | |
} | |
// OK | |
nonisolated func getData(url: URL) async -> (Data, URLResponse) { | |
try! await URLSession.shared.data(from: url) | |
} | |
} | |
#Preview { | |
TestView() | |
.frame(width: 300, height: 300) | |
} |
「Actor
はデータの非同期操作のために作られてる」感じがする。
外野から使ってみた雰囲気だけだけれども。
参考
【Swift】 @Observable に @MainActor をつけると おまえは 実質 ViewModel だろ #Swift #プログラミング #ioshttps://t.co/EbB0QksJX3
— chanzmao (@maochanz) July 7, 2024