LoginSignup
11
10

More than 3 years have passed since last update.

iOS13のメインスレッドチェック厳格化とNotificationCenterの組合せでつまづいた

Last updated at Posted at 2019-12-21

メインスレッドチェックの厳格化

元々、「UIの更新はメインスレッドで行うべし」というお約束はありましたが、Xcode11より、

  • iOS13ではサブスレッドからUIコンポーネントを変更するとクラッシュするようになりました。
  • サブスレッドからUIコンポーネントに「アクセスしただけ」で警告されるようになりました。
    • ビルド時の静的チェックではなく、ランタイムで該当コードを通ると紫にハイライトされます

NotificationCenterのスレッド

  • Notificationのpost元がメインスレッドであれば、addObserverで設定した関数(以下observer関数)はメインスレッドで処理されます。
  • Notificationのpost元がサブスレッドであれば、observer関数はサブスレッドで処理されます。

私のつまづき経緯

  • 既存のNotificationをpostするコードをサブスレッド処理の中に新たに追加
    • その既存Notificationは今までメインスレッドからしかpostしていなかった
    • 複数のobserver関数のうち一つが、サブスレッドでの動作を想定していないコードになっておりクラッシュが発生

サンプルコード

import UIKit
class ViewController: UIViewController {

    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var button: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(notify(_:)),
                                               name: Notification.Name("Notification"),
                                               object: nil)
    }

    @IBAction func tappedButton(_ sender: Any) {
        // (A)
        NotificationCenter.default.post(name: Notification.Name("Notification"),
                                        object: nil,
                                        userInfo: ["result": "This is Main Thread"])
        request()
    }

    func request() {
        guard let url = URL(string: "https://www.ibm.com/jp-ja") else { return }
        let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
            // (B)
            NotificationCenter.default.post(name: Notification.Name("Notification"),
                                            object: nil,
                                            userInfo: ["result": "This is Sub Thread"])
        }
        task.resume()
    }

    @objc func notify(_ notification: Notification) {
        // (A)のpostを受け取った場合は問題ないが、(B)のpostを受け取ると警告が出る
        print("text: \(label.text ?? "")")
        if let userInfo = notification.userInfo, let str = userInfo["result"] as? String {
            // (A)のpostを受け取った場合は問題ないが、(B)のpostを受け取るとクラッシュする
            self.label.text = str
        }
    }
}

横展開調査が面倒…

  • NotificationCenterによって通知する設計は処理の流れが追いづらく、影響範囲や発生条件を特定するのが大変
  • (UITestを書かない限り)手作業で動かしてみないと検証できない

再発防止策

observer関数内でUIコンポーネントにアクセスする時は、とにかく DispatchQueue.main.async で囲むことを徹底!
(精神論…:sweat_smile:

11
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
10