Swift 6.2+ の isolated deinit が嬉しい

@MainActor に隔離されたプロパティや non-Sendable なクラスのプロパティを deinit で触ろうとすると、コンパイラに怒られてしまいます。 

  • Main actor-isolated property 'xxx' can not be mutated from a nonisolated context
  • Cannot access property 'xxx' with a non-Sendable type 'XXX' from nonisolated deinit

Swift 6.2 からは、 isolated deinit を使うことで安全にアクセスすることができるようになりました。

[SE-0371] Isolated synchronous deinit

github.com

アクターに分離されたクラスのデイニシャライザを分離済みとしてマークする機能が導入されたことで、デイニシャライザはクラス内の他の場所にあるデータに安全にアクセスできるようになりました。少し具体例をあげてみます。

安全にキャンセル処理をする

@MainActor 隔離されたプロパティはもちろんですが、プロパティがnon-Sendableなクラスの場合にも役立ちます。例えば、 GraphQL の watcher をキャンセルするときに便利です。外部のサードパーティー側で、Sendable 対応が進んでいたとしても、 プロジェクト側の事情で non-Sendable なままのバージョンで開発を進めていることなどがあると思います。

以下のように、 GraphQLQueryWatcher が non-Sendable だった場合、 deinit でキャンセル処理をすることができません。 Swift 6に移行する過程で ViewModel を @MainActor に隔離した場合、その時に ViewonDisappear 経由でキャンセルするようにして回避することもできますが、別画面をモーダルで表示した時に、 watcher を生存させておきたいというケースでは困ってしまいます。(表示されてない時にVMwatchし続けている必要は基本的にはないと思いますが...)

@MainActor
final class ViewModel: ObservableObject {
    private var watcher: GraphQLQueryWatcher<SomeQuery>?

    deinit {
        watcher?.cancel()
    }
}

そういったケースでは isolated deinit を使うことで、 onDisappear でキャンセルするようにしなくても済むようになったりします。

@MainActor
final class ViewModel: ObservableObject {
    private var watcher: GraphQLQueryWatcher<SomeQuery>?

    isolated deinit {
        watcher?.cancel()
    }
}

そのほか、deinit で触りたいために、 無理に nonisolated なプロパティにしてしまっている箇所なんかも見直したりできそうです。

2026/02/04 追記

isolated deinit を使ったクラスを含むテストが同期なものの場合、 ランタイムクラッシュするようでした。その場合、テストを非同期にするとこれは回避できました。

github.com