アイリッジ開発者ブログ

アイリッジに所属するエンジニアが技術情報を発信していきます。

OS依存バグには#unavailableも活用しよう

こんにちは、開発部第1グループの西岡です。
2022年もあと残り僅かとなりましたが、皆さんはXcode14対応はもうお済みでしょうか?
Xcode対応後に複数のOSバージョンで動作確認や回帰テストをしていると、一部のOSバージョンのみでバグが見つかることがあります。
そんな時、影響範囲を絞ったソースコード修正を行うことがあるかと思います。
今回はそんなバグ修正に関連した話題を紹介したいと思います。

はじめに


 アプリ開発やリリース後の運用をしていると、特定のOSバージョンでのみ発生する不具合に出くわすことがあります。iOS15以上では再現しないけど、iOS14.7以下では発生するといった形で表出する、いわゆるOSバージョンに依存した不具合のことです。もちろん、このようなタイプの不具合は最新XcodeバージョンやAPIバージョンにアップグレードした際でも時々目にします。

 このような不具合を修正する場合、正常に機能しているOSバージョンには影響が及ばないように修正を行いたいです。もちろん発生しているOSバージョン範囲は特定できている必要はありますが、何よりも利用頻度が高い機能やビジネスにインパクトが大きな箇所だったりすることもあるので、数行のソースコード修正でも考慮しておきたいポイントだといえます。

 そんな特定のOSバージョン(※)に対して処理を分けたい場合、Swiftで重宝するのがバージョン要求の指定 #availableです。

※他に NSFoundationVersionNumber でFloatで比較を使うという選択肢もありますが、最近は主流ではないため本記事では取り上げないことにします。

OSバージョン指定と条件分岐での悩み


 #available を利用するケースとしては、新たに追加されたAPIが特定OSバージョン以上で利用可能だったり、特定の端末のみに限定するなど、利用可能な対象を指定するには非常に便利な表現です。しかし一方で、先述したような条件を当てはめてみようとするとやや面倒なことになります。

 例えば、実行環境がiOS13未満で発生する不具合に#availableを用いて対処すると以下のような書き方になるケースです。

// 今まではこう書いていた
if #available(iOS 13, *) {
        // do nothing
} else {
    // 修正コードを書く
}

 条件分岐で真だった場合のstatementでは1番大事な関心事を扱えず、else以降で修正コードを記述しがちです。もし仮にguard節で置き換えたとしても根本的な状況は変わりません。

 また、通常の条件分岐であれば否定 ! を付け加えることもできますが、#available ではそれができません。仮に否定を使うにしても、結局は独自に定義したメソッドやプロパティで回り道が必要になります。

#unavailableとは


実は… Swift5.6(Xcode13.3)で #unavailableという構文が追加されています!

if #unavailable(iOS 13.0) {
    // iOS13.0未満で実行したい処理を書く
}

Swift5.6バージョン自体はXcode13.3からサポートされていたとはいえ、それまでXcode13.3未満で開発していた経緯から、Xcode14対応後はちょっとだけ幸せな気分を味わえます。

詳しい説明はこちらもご覧下さい↓ github.com

#unavailableは先述の課題を見事に解決してくれます💯💯
以下の例では、iOS14.0未満 (iOS13.7以下)でのみ処理することになります。

if #unavailable(iOS 14.0) {
    // 修正コードを書く
}

#unavailableの注意点


Asterisk(*)は不要

1つ目の注意点として#unavailable(iOS 15, *)だとエラーになることです。

初期段階のプロポーサルでは登場していたようですが、議論の末#unavailable からAsterisk(*)の部分は除外される経緯を辿っているようです。

// コンパイルエラーになる
#unavailable(iOS 15.0, *)

// コンパイルOK
#unavailable(iOS 15.0)

実行から除外したいOSバージョンを指定する

2つ目の注意点としては、構文に指定したOSバージョンは含まない(実行を回避したい下限OSバージョンを書く)ということです。

例えば、iOS13.7以下でバグが発生しており、iOS14.0バージョン以降では正常に動作しているケースについて考えてみましょう。

OSバージョン テスト結果 修正後の追加処理
iOS14.0.1 OK 不要
iOS14.0 OK 不要 ← #unavailableで指定する
iOS13.7 NG 必要
iOS13.6.1 NG 必要

このケースでは修正箇所を実行させたい対象はiOS13.7以下であり、iOS14以上では修正箇所の処理を実行させたくないので、 #unavailable(iOS 14.0) と指定する必要があります。ここは#available構文とはやや異なる部分なので要注意です。

※英単語を文字通り読むと「iOS14が無効」という意味なので、ソースコードも英語のまま解釈するのをお勧めしたいところです😇

Summaries


  • Swift5.6(Xcode13.3)から#unavailableを利用できる。
  • #unavailableではAsterisk(*)が不要だった。

References


forums.swift.org docs.swift.org developer.apple.com