SwiftはObjective-Cの知識があればある程度書けますが、Swift固有の機能もあります。
今回はそういった機能について紹介し、Objective-C使いがSwift初心者を卒業するためのTipsについて紹介します。
enum型の中身にStringを使う
Objective-CはC言語の拡張なので、enumの値としてIntegerの値しか扱うことができません。
typedef NS_ENUM (NSUInteger, kIconSize) {
kIconSizeSmall,
kIconSizeMedium,
kIconSizeLarge,
kIconSizeOriginal
};
SwiftからはInteger以外にもStringなどが扱えます。
enum kIconSize : String {
case Small = "Small"
case Medium = "Medium"
case Large = "Large"
case Original = "Original"
}
// .rawValue で値を取り出す
println(kIconSize.Small.rawValue)
// -> "Small"
Swiftのswitch文ではStringの値もマッチさせることができるので、こういう使い方もできます。
let size = kIconSize.Small
switch size {
case .Small:
// ここにマッチ
println("Small Size") // -> "Small Size"
case .Medium:
println("Medium Size")
case .Large:
println("Large Size")
case .Original:
println("Original Size")
}
enum型にメソッドを追加する
Objective-Cではenum型のデータに関連したメソッドを定義することはできませんでした。その代わりとして、グローバルスコープで以下のような関数を定義していたと思います。
- (NSString *)iconSizeToString:(kIconSize)iconSize
{
switch (iconSize) {
case kIconSizeSmall:
return @"Small";
case kIconSizeMedium:
return @"Medium";
case kIconSizeLarge:
return @"Large";
case kIconSizeOriginal:
return @"Original"
}
}
Swiftではenum型がより使いやすくなり、メソッドが追加できるようになりました。
[補足]コメント欄より:プロパティに関しては、通常のプロパティ(stored property)は追加できませんが、computed propertyなら定義できます。
これにより、enum型のデータとそれに対する振る舞いをカプセル化することができます。
enum kIconSize {
case Small
case Medium
case Large
case Original
func sizeName() -> String {
switch self {
case .Small:
return "Small"
case .Medium:
return "Medium"
case .Large:
return "Large"
case .Original:
return "Original"
}
}
}
println(kIconSize.Small.sizeName())
// "Small"
Associated Value
Swiftのenum型は値を格納することができます。この機能を Associated Value と呼びます。関数型言語を使ったことある人には馴染みあるかもしれませんが、OCamlではヴァリアントと呼ばれます。
以下の例では Barcode という名前のenum型を定義していて、UPCA, QRCodeの2つのCaseがあります。UPCA は Int の値を4つ、QRCode は String の値を1つ格納することができます。
// 定義
enum Barcode {
case UPCA(Int, Int, Int, Int)
case QRCode(String)
}
まずBarcode型のデータを生成してみます。
Barcode.QRCodeの場合、初期化時に格納する値にStringを指定します。
// Barcode型の初期化
let barcode = Barcode.QRCode("ABCDEFGHIJKLMNOP")
// ↓はコンパイルエラーになる
Barcode.QRCode()
格納されたデータは switch 文を使って取り出せます。
switch barcode {
case .UPCA(let numberSystem, let manufacturer, let product, let check):
println("UPC-A: \(numberSystem), \(manufacturer), \(product), \(check).")
case .QRCode(let productCode):
println("QR code: \(productCode).")
}
// prints "QR code: ABCDEFGHIJKLMNOP."
格納された値を取り出すときにいちいち switch 文を書くのはめんどくさいので、メソッド化して値を取り出すようにしましょう。
enum Barcode {
case UPCA(Int, Int, Int, Int)
case QRCode(String)
func qrcode() -> String? {
switch self {
case QRCode(let qrcode):
return qrcode
default:
return nil
}
}
}
ちなみに、Swiftでよく出てくるOptional型も値を格納できるenum型として定義できます。
(enum型はGenericな型を格納することもできます。)
enum Optional<T> {
case Some(T)
case None
}
var x = Optional<Int>.None // nil と同じ意味
x = Optional<Int>.Some(10) // Optional(10) と同じ意味
おまけ:Result パターン
enum型の応用として、Result パターンもよく使います。
Resultパターンは結果を表すデータを表現できます。結果は成功の場合と失敗の場合のいずれかを表します。
以下がResultパターンの定義です。
enum Result<T> {
// 成功の場合
case Success(T)
// 失敗の場合
case Error(NSError)
}
Objective-C では非同期な処理の結果を受け取るときに、以下の様な結果とエラーの2引数を受け取るようなcompletion handler を設定していたかと思います。
- (void)doAsyncRequestWithCompletion:(void (^)(NSDictionary *response, NSError *error))completionHandler;
Result パターンを使えば、そのような必要はなくなります。
以下は Result パターンの使用イメージです。
// T にはレスポンスのモデルクラスを指定する
class HTTPClient<T> {
// 通信が失敗した時のエラー情報を返す
func error() -> NSError? { ... }
// 通信が成功した場合のレスポンスを返す
func getResult() -> T { ... }
// 非同期で結果が返ってくるメソッド
func getResultAsync(completion:(Result<T> -> Void)) {
// 処理に失敗した場合
if let error = self.error() {
completion(Result<T>.Error(error))
return
}
// 成功の場合
let result = self.getResult()
completion(Result<T>.Success(result))
}
}
// 使用例
HTTPClient<UserModel>().getResultAsync { result in
switch result {
case .Error(let error):
println(error)
case .Success(let resp):
println(resp)
}
}