73859


下記コードの `self.message = "OK"` と ` self.message = "NG"` のところで以下のエラーが発生します。

[code]

    Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates. 
    
[/code]

ViewModel:

[code]

    import Foundation
    import Combine
    import Network
    
    class ContentViewModel: ObservableObject {
        @Published var message = ""
        
        private let monitor = NWPathMonitor()
        private let queue = DispatchQueue(label: "network_monitor")
        
        init() {
            monitor.pathUpdateHandler = { path in
                if path.status == .satisfied {
                    self.message = "OK"
                } else {
                    self.message = "NG"
                }
            }
            monitor.start(queue: queue)
        }
    }
    
[/code]

View:

[code]

    import SwiftUI
    
    struct ContentView: View {
        @ObservedObject var contentViewModel = ContentViewModel()
        
        var body: some View {
            VStack {
                Text(self.contentViewModel.message)
            }.frame(
                maxWidth: .infinity,
                maxHeight: .infinity
            )
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    
[/code]

警告を回避し、メインスレッドで動かすのであれば

[code]

    if path.status == .satisfied {
        DispatchQueue.main.async {
            self.message = "OK"
        }
    } else {
        DispatchQueue.main.async {
            self.message = "NG"
        }
    }
    
[/code]

と `DispatchQueue.main.async` で囲めば解決しますが、警告には `via operators like receive(on:)`
といってるので `receive(on:)` を使う書き方があるのでしょうか？ そしてそれはどのように書くのでしょうか？


CC BY-SA 4.0
2021-02-08T04:31:56.113
73853
2021-02-08T05:59:30.890
2021-02-08T05:02:18.530
3060
9008
question
swift swiftui
SwiftUIでObservedObjectなオブジェクトでメインスレッドで実行するよう設定するSwiftUIらしい書き方が知りたい


> 警告には `via operators like receive(on:)` といってるので `receive(on:)`
> を使う書き方があるのでしょうか？

警告の文言は、「バックグラウンドスレッドからの`@Published`変数の更新」一般について出されるものなので、特に`NWPathMonitor`のようなクラスを意識したものではなく、「`URLSession.dataTask`のように`Publisher`が得られる代替APIが存在する場合」を念頭に置いたものだろうと思われます。

特に「`Publisher`が得られる代替API」が存在しない場合に、無理に`receive(on:)`を使う必要はないのではないでしょうか。

[code]

    class ContentViewModel: ObservableObject {
        @Published var message = ""
        
        private let monitor = NWPathMonitor()
        
        init() {
            monitor.pathUpdateHandler = { path in
                if path.status == .satisfied {
                    self.message = "OK"
                } else {
                    self.message = "NG"
                }
            }
            monitor.start(queue: .main) //<- ここで`DispatchQueue.main`を使う
        }
    }
    
[/code]

* * *

無理にでも`receive(on:)`を使うような形に書くと、こんな風になるでしょう。

[code]

    class ContentViewModel: ObservableObject {
        @Published var message = ""
        
        private let monitor = NWPathMonitor()
        private let queue = DispatchQueue(label: "network_monitor")
        
        private let subject = PassthroughSubject<NWPath, Never>()
        private var cancellable: AnyCancellable!
        
        init() {
            monitor.pathUpdateHandler = { path in
                self.subject.send(path)
            }
            monitor.start(queue: queue)
            cancellable = subject.receive(on: DispatchQueue.main)
                .sink {path in
                    if path.status == .satisfied {
                        self.message = "OK"
                    } else {
                        self.message = "NG"
                    }
                }
        }
    }
    
[/code]

[NWPathMonitor を Combine の Publisher
として扱えるようにする拡張](https://gist.github.com/YusukeHosonuma/0a39266dd5c2232d6d0a42f826adb8e6)なんてものも見つかりましたが、Networkフレームワークに公式にCombine系のAPIが追加されない限り、今回のような処理では大きなメリットは無いように思われます。


CC BY-SA 4.0
2021-02-08T05:59:30.890
73859
2021-02-08T05:59:30.890
13972
73853
answer