The Pragmatic Ball boy

iOSを中心にやってる万年球拾いの老害エンジニアメモ

UIViewControllerがMainActorだから安心できるわけではない

前回の記事でUIViewControllerとMainActorのことについてかきました。UIViewControllerはMainActorなのでMainスレッドで実行されることは保証されていて安心!かと思いきやそうでもなかったのです。

以下のMainActorであるViewModelをMainActorではないクラスから呼び出そうとするとコンパイル時にエラーになります。MainActorを使うことでこのようにコンパイル時にチェックされて安全です。

class
 Good {
    let viewModel = ViewModel() // Error: Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context
}

@MainActor class ViewModel {
    func hello() {
        print("hello: \(Thread.current.description)")
    }
}

UIViewControllerもMainActorなので上と同様なことをしてみます。 するとどうでしょうコンパイルに通ってしまいます。

class Bad {
    let vc = UIViewController() // エラーにならない
}

このBadというクラスをMainスレッド以外で生成するとクラッシュしてしまいます。

どうしてこうなっているかというとSwift5ではSwiftConcurrencyのSendableのチェックがゆるくなっているためです。 これは、SwiftConcurrencyに対応していないモジュールとのやりとりを楽にするためにSendableを完全に強制していないからです。 Swift5, 6でのConcurrencyの違いはこちらの Concurrency in Swift 5 and 6 などを見てみると思想を知ることができます。

Swift5系のXcodeでは、BuildSettingの Strict Concurrency Checking はデフォルトでMinimalになっています。 これをCompleteに変えると先程のBadというクラスはコンパイルエラーになるようになります。

なので Strict Concurrency Checking はCompleteにしておいたほうがより安全です。 まだかなり先になると思いますがSwift6ではCompleteになるようですので対応できるなら先に対応しておいたほうが後々のSwift6への移行も楽になるかと思います。

ただ既存のコードだと利用しているライブラリも直さなければならなかったりとかなり大変だったりするので、既存のプロジェクトはTargetedからはじめ、新規プロジェクトでは最初からCompleteにしてやるなどするとよいかなと思います。