@unknown default: の必要性について。SwiftのWarning: Switch covers known cases, but ‘{EnumType}’ may have additional unknown values, possibly added in future versions の対応

前提: Xcode Version 15.4 (15F31d)

Swiftを書いていて、以下のようなWarningが出ました。

Switch covers known cases, but 'Product.SubscriptionPeriod.Unit' may have additional unknown values, possibly added in future versions
extension StoreKit.Product.SubscriptionPeriod.Unit {
    var pigeonSubscriptionPeriodUnit: PigeonSubscriptionPeriodUnit {
        switch (self) { // Warning: Switch covers known cases, but 'Product.SubscriptionPeriod.Unit' may have additional unknown values, possibly added in future versions
        case .day:
            return .day;
        case .week:
            return .week;
        case .month:
            return .month;
        case .year:
            return .year;
        }
    }
}

このwarningは、以下の2行を追加すると消えます。

        @unknown default:
            fatalError() // TODO:

これは、将来enumにcaseが追加されたときのためのものというのは何となくわかっていました。

これまで何とも思ってなかったのですが 今回これも見て、ふと、これは不要ではないかと思いました。なぜなら、新しいcaseが追加されても、開発者がXcodeを更新してライブラリが新しくなれば、コンパイルエラーとしてcaseが追加されたことを検知できると思ったためです。

また、反対に、@unknown defaultがあることにより、caseが追加されてもコンパイルエラーが発生しなくなり、開発者は検知できなくて困るのではないかと思いました。

しかし、(iOSアプリ開発者にとって当たり前のことかもしれませんが)この理解は間違っていました。ここで例として取り上げているStoreKitのようなAppleのライブラリの多くはDynamic Libraryで提供されています。そのため、「開発者がXcodeを更新」するタイミングではなく、ユーザーのデバイスでiOSが更新されたタイミングで、このライブラリも更新されます(もし更新があれば)。そのときに このenumにcaseが追加された場合、switchのどのケースにも当てはまらずクラッシュする可能性があります。

@unknown defaultがあることで、ユーザーがiOSを更新した際に発生しうるクラッシュを防ぐことができます。

また、ビルド時に、コンパイラが、caseが追加されたことを警告してくれるとのことです。

参考までに、今回の例では、現時点ではcase @unknown defalutの場合、画面に「このサブスクリプションの期間は、サポートされていません」のような趣旨のメッセージを出すことにしました。(補足として、上のコードは、iOSのSubscriptionのProductを取得して、画面に表示するための処理です。) こうすることで、@unknown defaultがないことでクラッシュさせてしまう場合と異なり、これまで想定してたcaseのProductをユーザーが購入する手段を維持することができます。

最後にまとめると、この@unknown defaultは、iOS更新による将来のcase追加に伴うクラッシュを防ぎつつ、開発者に警告でcaseの追加を教えてくれるためのものなのでした。

参考

警告メッセージでググっても@unknown defaultが必要な理由を説明してくれるような記事を自分は見つけらませんでした。そのためこの記事は、ChatGPTに聞いた内容を元に書きました。ChatGPTの回答のうち、参考になった部分を下記の通り載せておきます。

例えば、アプリがApp Storeにリリースされており、新しいiOSやmacOSのバージョンでenumに新しいケースが追加された場合、そのenumを使ったコードがリビルド
されない限り、既存のswitch文が新しいケースに対応できない状態になる可能性があります。@unknown defaultがあれば、新しいケースが追加されても、アプリがクラッシュすることなく安全に処理できます。

この点で、@unknown defaultは新しいケースが追加されたときにコンパイ
ルエラーではなく警告を出すことで、開発者にその事実を認識させつつ、アプリのクラッシュを防ぎます。

仮にAppleが将来 SubscriptionPeriodUnit.quarter という新しいケースを追加したとしても、アプリが古いSDKでコンパイルされている場合、.quarter に対処するコードがないとクラッシュする可能性があります。@unknown defaultを使っておくと、少なくともアプリがクラッシュせずに、"Unknown case" という形で処理が行われます。

コメント

タイトルとURLをコピーしました