Swiftでサーバから非同期にデータを取得して、それを画面に描画するという処理を書きたい。
はじめ、スレッドを意識せずに以下のようなコードを書いていたため、画面をタップしないとセルが描画されない、もしくは描画されるまで待っていると以下のようなエラーが出るという現象が起きていた。
エラー
This application is modifying the autolayout engine from a background thread, which can lead to engine corruption and weird crashes. This will cause an exception in a future release.
問題のあるコード
loadData() {
(task: AWSTask!) -> AnyObject! in
if task.error !== nil {
let alertView = UIAlertController(title: "エラー", message: "データの取得に失敗しました。", preferredStyle: .Alert)
alertView.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
self.presentViewController(alertView, animated: true, completion: nil)
} else {
self.tableView.reloadData()
}
return nil
}
ここでloadData()関数はAWS SDKを利用し、バックグラウンドスレッドで非同期にAWSからデータを取得する処理を担っているとする。loadData()はクロージャを受け取り、AWSと通信した後にそれを実行する。
通信が正常に終了した場合、self.tableView.reloadData()
が実行されることになるが、この処理はバックグラウンドスレッドで実行される。
しかし、SwiftではUIに関する操作をバックグラウンドスレッドで行ってはいけない。そのため上記のようなエラーが出力されていた。
これを解決するため、以下のようなコードに修正する。具体的には、dispatch_async(dispatch_get_main_queue())
を実行し、self.tableView.reloadData()
をメインスレッドで実行するようにしてあげれば良い。
loadData() {
(task: AWSTask!) -> AnyObject! in
dispatch_async(dispatch_get_main_queue()) { // 追加
if task.error !== nil {
let alertView = UIAlertController(title: "エラー", message: "データの取得に失敗しました。", preferredStyle: .Alert)
alertView.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
self.presentViewController(alertView, animated: true, completion: nil)
} else {
self.tableView.reloadData()
}
} // 追加
return nil
}
ちなみに、自分がメインスレッドにいるかどうかを以下のコードで調べることができる。
loadData() {
(task: AWSTask!) -> AnyObject! in
let currentThread = NSThread.currentThread()
print(currentThread.isMainThread) // false
dispatch_async(dispatch_get_main_queue()) {
let currentThread = NSThread.currentThread()
print(currentThread.isMainThread) // true
if task.error !== nil {
let alertView = UIAlertController(title: "エラー", message: "データの取得に失敗しました。", preferredStyle: .Alert)
alertView.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
self.presentViewController(alertView, animated: true, completion: nil)
} else {
self.tableView.reloadData()
}
}
return nil
}