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() }