Swift 言語の特徴
Swift2.2 です。特徴的だと思ったことを社内の勉強会用にまとめていきたいと思います。コードの動作は playground に貼り付けて確認してください。
型
型の制約
- 異なる型への代入や演算はできない
- 演算の結果は同じ型
- 異なる型へのキャスト
- メソッドによる変換
異なる型への代入や演算はできない
let a: Int = 1 let b: Float = 1.0 a + b // エラー
理由
開発者の意図していなかった動作を防ぎ、バグの発生を抑えるためじゃないかな。
演算の結果は同じ型
let a: Int = 1 let b: Int = 2 a / b // 0
理由
開発者の意図していなかった動作を防ぎ、バグの発生を抑えるためじゃないかな。
異なる型へのキャスト
Swift では、異なる型への代入ができないため、以下のような例では、見た感じ Int だと分かっていてもエラーとなる。
let list: [Any] = [1, "1"] let a: Int = list[0] // エラー(型は Any だよ)
as によるキャスト
let list: [Any] = [1, "1"] let a: Int = list[0] as! Int // 1 a.dynamicType //Int.Type
理由
型をしっかり意識して、意図しないコードは書かないようにとの教え? ※どんな時に役に立つか考える
メソッドによる変換
as は基本サブクラスへキャストする場合に用います。サブクラスでないクラスに変換しようとするとエラーになります
let a: Int = 1 let b = a as Float // エラー
サブクラスとの関係にない場合は Int や String など用意されているメソッドを利用して変換します。
Int(1.0) // 1 String(1.0) // "1.0"
型推論
Swift は静的型付けの為、ビルド(コンパイル)時に全ての変数の型が決まる。その為、全ての変数に対して型を指定する必要がある。
ただし Xcode が型を推測可能な場合は、型の指定を省略することができる。
let a: Int = 1 a.dynamicType // Int.Type let b = 1 b.dynamicType // Int.Type
型は省略可能だが、代入される値があって初めて推論が働くので、以下のような Xcode が型を推論できない場合は エラーを吐く
var a //エラー
Optional 型(オプショナル型)
- Optional 型の指定
- Optional〈Array〉は Array or nil ではない
- Optional の箱を開ける ! ( Forced unwrapping ) と ? ( Optional chaining )
- アンラップを楽に行う為の特別な構文
- クラスのプロパティの初期化
Optional 型の指定
nil が入る可能性がある変数は Optional 型を使う
let a: Int = nil // エラー
let a: Int? = nil a.dynamicType // Optional<Int>.Type
理由
nil に対してアクセスした際のエラーが多いので、安全なコードになるように、デバッグしてくれていると思っている。
Optional〈Array>〉は Array or nil ではない
Array に nil を代入したい時 Optional〈Array〉と宣言して Array or nil を持つ変数と Optional 型を捉えてしまうと Optional 型に対して ? と思うケースが多くある。 Array + nil として Optional 捉えるのではなく Optional 型という一つの型( Array とは異なる型)として理解しとく。
var list: [String] = ["a", "b"] list.append("c") // ["a", "b", "c"]
ちょっとした違いだが、以下の場合はエラー
var list: [String]? = ["a", "b"] list.append("c") // エラー
Array 型では append メソッドが定義されているが、Optional〈Array>〉型は Array 型ではないので append メソッドが定義されていない。
この記事がとても参考になります。 SwiftのOptional型を極める
Optional の箱を開ける ! ( Forced unwrapping ) と ? ( Optional chaining )
Optional は旦那が釣りに行って帰って来た時のクーラーボックスと捉えるといいらしい。蓋を開けるまで、中に魚が入っているかどうかは分からない。
! は Forced unwrapping 。値が必ず入っていると分かるケースに使う。nil が来たらアプリがクラッシュする。クーラーボックスの中身を確認せずに、大根のツマをお皿に敷いて準備しているようなもん。旦那にトドメを刺す可能性あり。
struct Rice { func taku() -> String { return "hokahoka" } } var rice: Rice? rice = Rice() rice!.taku() // "hokahoka"
struct Rice { func taku() -> String { return "hokahoka" } } var rice: Rice? rice!.taku() // エラー(アプリだったらクラッシュ)
? は Optional Chaining といった使い方をする。Optional Chaining の返り値は必ず Optional 型になる。
struct Rice { func taku() -> String { return "hokahoka" } } var rice: Rice? rice = Rice() rice?.taku() // "hokahoka"
struct Rice { func taku() -> String { return "hokahoka" } } var rice: Rice? rice?.taku() // nil
アンラップを楽に行う為の特別な構文
強制アンラップは基本使わない。いくつか使うことを推奨されているケースがあるが。安全に倒す分、コードが長くなる。その為施策として、特別な構文を用意してくれている。
1. if let
var rice: Int? rice = 1 if let riceBall = rice { riceBall + riceBall // 2 riceBall.dynamicType // Int }
nil でなかったら if 文の中が実行される。 rice には必ず値が入っていることが保証されるので アンラップは必要ない。if let 構文を使わないとこんな感じになる
var rice: Int? rice = 1 if rice != nil { rice! + rice! // 2 rice.dynamicType // Optional<Int> }
使いどころ
- if let を利用しないと強制アンラップしないといけないので if let は積極的に活用する。
2. guard let
func taku() { var rice: Int? guard let riceBall = rice else { return // 実行される } riceBall + riceBall // 実行されない。 rice が nil でなければ実行される。 }
使いどころ
- ガード節として、早期リターンしたい場合に利用する。階層が深くなるのを避けられるので、こちらも用途に合わせて積極的に活用する
3. オプショナルチェーン
struct Rice { func taku() -> String { return "hokahoka" } } var rice: Rice? rice?.taku() // nil
rice が nil だったら taku() は実行されず nil が返却される。オプショナルチェーンを使わないで書くとこんな風になる。
struct Rice { func taku() -> String { return "hokahoka" } } var rice: Rice? if let hokahokaRice = rice { hokahokaRice.taku() // nil の場合は実行されない }
使いどころ
- オプショナルチェーンを使うとコード量を減らせるので積極的に使おう。
4. map
let riceBall: Int? = nil let result: Int? = riceBall.map { $0 + $0 } // nil
let riceBall: Int? riceBall = 1 let result: Int? = riceBall.map { $0 + $0 } // 2
使いどころ
rice + rice を map を利用して行っている。オプショナル型の演算は今まで if let や guard let を利用して書いてきたが map を利用することで簡潔に書くことができる。 map をきちんと理解して利用できるとこは積極的に利用する。
クラスや struct のプロパティの初期化
初期値を設定できない時は Optional 型で宣言する 初期値の設定には 2つの方法がある 1. 変数名の後に初期値を設定 2. init の中で初期値を設定
struct Rice { var riceBall: Int? // OK }
struct Rice { var riceBall: Int // 初期値が設定されてないのでエラー }
struct Rice { var riceBall: Int = 1 // OK }
struct Rice { var riceBall: Int // OK init() { self.riceBall = 1 } }
値型と参照型
変数への代入
値型: データそのものをメモリ管理。 参照型: データとデータのアドレスを別々に管理。変数はデータへの参照アドレスを持つ。
こちらの記事が大変勉強になりました。ありがとうございます。 Swiftで値型と参照型の違いを理解する
値型
struct Rice { var riceBall = 1 } var rice = Rice() var rice2 = rice rice.riceBall = 2 rice2.riceBall // 1
値型は代入する時データのコピーを作成する。 rice2 = rice の時に rice2 は rice と別のメモリ領域に値をコピーされるので rice の変更の影響を受けない。
参照型
class Rice { var riceBall = 1 } var rice = Rice() var rice2 = rice rice.riceBall = 2 rice2.riceBall // 2
参照型は代入する時、データの参照先であるアドレスをコピーする。 rice2 = rice の時、値型と違って rice のデータはコピーされず rice のデータの参照先を rice2 に渡す。なので rice の値を変更すると rice2 の参照するデータは rice と同じデータなので値が変更される。
使いどころ
積極的に値型である struct を採用するのが、意図しない動作につながるらしい。ここはまだ実感がなく、気づきがあったら更新します。こちらの記事で積極的に値型を利用するようにまとめられています。 Swift の値型で複雑性をコントロール
値型と参照型に対する let と var
値型
struct Rice { var riceBall = 1 } var rice = Rice() rice.riceBall = 2 // 2
struct Rice { var riceBall = 1 } let rice = Rice() rice.riceBall = 2 // エラー
let rice は値型なので riceBall の値は変更できません。
参照型
class Rice { var riceBall = 1 } var rice = Rice() rice.riceBall = 2 // 2
class Rice { var riceBall = 1 } let rice = Rice() rice.riceBall = 2 // 2
let rice は参照型なので riceBall の値の変更ができます(変数はデータの参照先を持っているだけで、データそのもの(raceBall)は別のメモリに存在しているから)。rice そのものへの代入はエラーになります。
class Rice { var riceBall = 1 } let rice = Rice() let rice2 = Rice() rice = rice2
メソッド
同名メソッド
func niginigi(count: Int) { print("\(count)個握ります") } func niginigi(g: Int, count: Int) { print("\(g)gのお米を\(count)個握ります") }
引数の異なる同名のメソッドを定義できます。上記のように定義すると niginigi と Xcode に入力すると補完機能が働いて、以下の2つのメソッドが候補に出力されます。
func niginigi(count: Int) func niginigi(g: Int, count: Int)
引数のデフォルト値
func niginigi(count: Int = 1) { print("\(count)個握ります") }
メソッドの引数には、デフォルト値を設定できます。上記のように定義すると niginigi と Xcode に入力すると補完機能が働いて、以下の2つのメソッドが候補に出力されます。
func niginigi() func niginigi(count: Int)
名前付きタプル
関係性のある要素を気軽にグルーピングできる。dictionary でも同じようなことができるが、名前付きタプルは Xcode の補完が効くので便利。
let Tokyo = (lat: 35.681382, lng: 139.766084) Tokyo.lat Tokyo.lng
プロパティ
計算型プロパティ
let height = 5 var square: Int { return height * height } square // 25
square のような変数を計算型プロパティと言います。
let height = 5 let square: Int { return height * height // エラー }
計算型プロパティは必ず var で宣言する必要があります。なぜなら、計算型プロパティは呼び出されたタイミングで計算が実行される為、状態によって変化する値となる為です。
var height = 5 var square: Int { return height * height } square // 25 height = 2 square // 4
ジェネリクス
こちらの記事がとても参考になります 【Swift】ジェネリクスが使われているメソッドを理解する
ジェネリクスによる複数の型の対応
例えば、2つの同じ型の要素を交換するメソッドを作りたいとします。 Swift はメソッドの引数には必ず型を指定する必要がある為、単純に考えると以下のような感じで、各型に対してメソッドを定義する必要がありますが、非常に冗長です。
func swapTwo(inout a: Int, inout b: Int) // Intを交換する func swapTwo(inout a: String, inout b: String) // Stringを交換する
そこでジェネリクスの登場です。以下のように指定することで、任意の型を引数に受け取れるようになります。
func swapTwo<T>(inout a: T, inout b: T) // 好きな型のものを交換できる
どんな型でも受け入れられる AnyObject でいいのではないかと思いますが、以下の場合は a と b にそれぞれ異なる型を入れられるようになってしまうので、同じ型である必要がある場合はジェネリクスを利用するのが適切です。
func swapTwo(inout a: AnyObject, inout b: AnyObject) // 好きな型のものを交換できる