UIの更新はmain threadから行う

iOSからAPIにリクエストを送って返ってきたjsonを画面に表示させたい、というシンプルな実装をしようとしたら詰まったのでメモする。まず、以下のように実装したがエラーが発生した。

 @IBOutlet weak var myTextView: UITextView!
    func makeData() {
        let url = URL(string: "https://api.openweathermap.org/data/2.5/forecast?q=Tokyo,jpn&APPID=xxxxx")!
        let req = URLRequest(url: url)
        let task = URLSession.shared.dataTask(with: req) { (data, res, error) in
            guard let data = data else {
                return
            }
            do {
                let obj = try JSONSerialization.jsonObject(with: data, options: [])
                self.myTextView.text = "\(obj)"
            } catch let e {
                print(e)
            }
        }
        task.resume()
    }

デバッガに表示されたエラーは以下の通り。

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Only run on the main thread!'

main thread

iOSではmain threadという考え方があり、UIの描画や更新などはこのmain threadで行なっている。また、UIの更新はmain threadで行う必要がある(ここではmainではないthreadをbackground threadと呼ぶことにする)。 この辺りに関しては、GCDも含めてIntro to iOS threading.での説明がわかりやすかった。日本語だとmixiの研修資料がよかった。

上記のコードでいうと、URLSessionを使用した通信はbackground threadで行われており、background threadでself.myTextView.text = "\(obj)"のようにUIを更新しようとしたためエラーが発生していた。

では上記のコードでどのようにmain threadでUIを更新するかというと、DispatchQueue.main.syncを利用する。DispatchQueue.main.syncのブロック内に記述されたコードはmain threadで実行される。修正後のコードは以下の通り。

 @IBOutlet weak var myTextView: UITextView!
    func makeData() {
        let url = URL(string: "https://api.openweathermap.org/data/2.5/forecast?q=Tokyo,jpn&APPID=xxxxx")!
        let req = URLRequest(url: url)
        let task = URLSession.shared.dataTask(with: req) { (data, res, error) in
            guard let data = data else {
                return
            }
            do {
                let obj = try JSONSerialization.jsonObject(with: data, options: [])
                DispatchQueue.main.sync {
                    str = "\(obj)"
                }
            } catch let e {
                print(e)
            }
        }
        task.resume()
    }