簡単なバージョン番号の比較方法

最近、頑張って比較してるのにバグっている見たコードをみました。簡単な比較方法を知らない人もいるのかもと思って書いてみます。

たまに求められるバージョン番号比較

何らかの理由でバージョン番号の比較をしたいときってありますよね?○○バージョン以上なら△△するとか。求められる内容によって

  • Class オブジェクトが生成できるかどうか?
  • respondsToSelector: で期待しているセレクターが呼べるか?

とか方法はいろいろありますが、今回は文字列での比較の話です。

文字列ベースで比較

文字列ベースでのバージョン番号の比較ってどうしてますか?

integerValue とか floatValue とかで数値化して比較してますか?最近見たコードでもそのような比較をしてました。でも、バージョン番号って 「5.1」とかなら単純に数字にしてしまって比較できますが、「5.1.1」とかだと少数でもないですよね?バラして数値化して比較することもできますがちょっと面倒ですし、バグりそうです。実際に僕が見たコードはバグってました。

NSNumericSearch

こんなときは NSString の

- (NSComparisonResult)compare:(NSString *)aString options:(NSStringCompareOptions)mask

を使うと簡単です。

compare:options: の option 引数で NSNumericSearch を指定すると数字を含んだ文字列を数字として評価して結果を返してくれます。これは Mac の Finder でのファイル名ソートにも使われています。 "file_1.txt", "file_5.txt", "file_10.txt" とかってファイルをソートするとちゃんと 1 の 後には 5, 10 と続きますよね?OSとかによっては 1 の後が 10, 5 ってソートされて残念な気分になったことがあります。

具体例 1

NSComparisonResult result = [@"4.1.1" compare:@"3" options:NSNumericSearch];  
// result is "NSOrderedDescending"

上記の場合は Descending なので

"4.1.1" > "3"

ということです。

具体例 2

NSComparisonResult result = [@"5.1.1" compare:@"6.0" options:NSNumericSearch];  
// result is "NSOrderedAscending"

つまり

"5.1.1" < "6.0"

具体例 3

NSComparisonResult result = [@"6.1" compare:@"6.1" options:NSNumericSearch];  
// result is "NSOrderedSame"

つまり

"6.1" = "6.1"

ですね。

具体例 4

こんな変な比較も人間的な感覚にマッチした比較結果を返してくれます。

NSComparisonResult result = [@"4.0.0.1.0.0" compare:@"4.0.0.0" options:NSNumericSearch];
// result is "NSOrderedDescending"

具体例 5

僕はこんな感じのコードを UIDevice のカテゴリメソッドで生やしています。

+ (BOOL)cs_isOSVersionGreaterThanOr:(NSString *)version
{
    NSString *currentVersion = [[self currentDevice] systemVersion];
    NSComparisonResult result = [version compare:currentVersion options:NSNumericSearch];
    return (result == NSOrderedAscending || result == NSOrderedSame) ? YES : NO;
}

注意点

  • 引数に渡す文字列が nil の場合には戻り値が不定(Reference より)
  • レシーバ側が nil の場合は 0 が返るので結果が NSOrderdSame になる
  • [@"6" compare:@"6.0" option:NSNumericSearch] の結果が NSOrderedAscending を返してしまう(2013/02/25 追記)

注意点が増えました 3番目の問題は @norio_nomura さんに指摘していただきました。これは地味にうざい挙動です。やらかしてしまいそうです。

そつなく

compare:options: は Foundation.framework で結構古くから実装されていますし Mac OS でも実績があるはずの枯れたものですのでバグがある可能性はあまり考えられません。注意点に上げたポイントさえ気をつければヘタに文字列を分割して数値化して比較するコードを手で書くよりも簡単で無難ですのでどんどん使いましょう。