はじめに
iOSアプリでは現在実行中のバージョンを前回起動した際のバージョンと比較したり、特定のバージョンと比較したりということがあります。そういう時、バージョン表記としてStringで取得できる"1.0.1"のようなカンマが2つある文字列はそのままでは数値として成立しないので比較することが出来ません。その解決方法として、簡単なのはStringのcompareメソッドで比較したりしますよね。ただ、compareするにしても演算子(オペレータ)オーバーロードを使えば表現が分かりやすくなりますよという話を書いておきます。
3行くらいでやりたいことをまとめると
- iOSで取得できるバージョンの文字列は大抵カンマが2つありそのまま数値にして比較できない
- Stringの文字列比較で大小を比べられるため大抵はそれをメソッド化する
- メソッド化するにしても
Version
という型を演算子で比較できるようにしとけば分かりやすい表現になる
struct でバージョンを表現する
まずstructでVersionを定義する
struct Version {
let rawString: String
}
次に演算子オーバーロードを定義します。
func < (lhs: Version, rhs: Version) -> Bool {
if lhs.rawString.compare(rhs.rawString, options: .numeric) == .orderedAscending {
// バージョンの文字列を数値として比較してます。"1.0.1" < "2.0" なら .orderedAscending
return true
}
return false
}
compareの戻り値は enum ComparisonResult なので .orderedAscending
などが返ってくるんですが、そういうのを隠蔽化したいという話ですね。
利用例は
Version(rawString: "1.0.0") < Version(rawString: "2.0") // true
Version(rawString: "1.0.0") < Version(rawString: "0.2") // false
このままで何のバージョンにでも使えるようなVersionが作れました。しかし、現在のアプリのバージョンが一番使うじゃないですか。というかそのために作ってるので次のように取得できるようにします
extension Version {
static let currentApp = Version(rawString: Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String)
}
利用例は次のようになります。
print(Version.currentApp.rawString) // 1.0
Version(rawString: "0.1") < Version.currentApp // true
Version(rawString: "1.1") < Version.currentApp // false
Version(rawString: "10.1") < Version.currentApp // false
ちなみにXcode8.3.2のPlaygroundでは CFBundleShortVersionString で取得するバージョンは1.0になっていましたのでそれとの比較になります
ただ、Versionのextensionでアプリのバージョンを返すのが少し嫌な感じです。バージョンという型がアプリのことを知っているのってやりすぎかもしれませんね。
なので、struct CurrentApp というアプリ情報を返す型が、バージョンを返すことを考えてみましょうか。
struct CurrentApp {
static let version = Version(rawString: Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String)
}
// 利用例もあわせて書くと
Version(rawString: "10.1") < CurrentApp.version
CurrentAppはバージョンの他にもビルド用の何かの情報を出したりするのに良いでしょうね。
その他
struct Versionではinitを省略したため自動で暗黙的にイニシャライザが作られたので、 rawString
という外部引数名が出てきましたが、このrawString
を外部引数名として露出したくない時は、もちろんinitで外部引数名を省略すれば良いのでこちらのほうが短くて好みの人は多そうです。
struct Version {
let rawString: String
init(_ rawString: String) {
self.rawString = rawString
}
}
Version("0.1") < Version("1.1") // true
おわりに
このような比較の他にも、struct Versionのメリットとして現在のバージョンが 1..<2の間かどうか、などの際も演算子をオーバーロードすれば、範囲内かどうかの表現を演算子で表現できて、読みやすいコードになるかもしれないですね(そんなふうな事をするアプリにならないのが一番ですが)。
@yimajo 役割がはっきりして良さそうですよね。そういえば思い出しましたけど Swift Package Manager にも Version 表現する構造体があったりしました。 #swift https://t.co/HhRmOLB9HI
— 熊谷 友宏 (@es_kumagai) 2017年5月16日