Swiftで使える様々なテクニックを紹介します。
guard構文内のブロックではfatalError()
やabort()
が呼べる
例えば、こんな使い方ができます。
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCellWithIdentifier("CustomCell", forIndexPath: indexPath) as? MyCustomCell else {
abort() // Storyboard で Custom Class を設定していないと思われる
}
...
return cell
}
tableView(_:cellForRowAtIndexPath:)
は UITableViewCell
を return する必要があり、return nil
も throw
もできません。
適当な UITableViewCell
オブジェクトを作って返す方法もありますが、こうすることで問題検出しやすいのではないでしょうか。
if let, guard let 構文は複数の式を記述できる
if let
guard let
構文で unwrap したオブジェクトから更に unwrap したい場合があると思います。このような場合 if let
をネストしたり、複数 guard let
を書く必要はありません。
下記のようにカンマ区切りで複数の式を書くことで、先頭から順次評価されます。
unwrap できない(式の結果が nil)場合、後続の式は評価されません。
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
guard let identifier = segue.identifier else {
return
}
switch (identifier) {
case "showDetail":
// アイテム詳細画面への遷移に備える
guard
let cell = sender as? UITableViewCell,
let indexPath = self.tableView.indexPathForCell(cell),
let item = self.itemAtIndexPath(indexPath), // indexPath に対する Item オブジェクトを取得
let itemDetailViewController = segue.destinationViewController as? ItemDetailViewController
else {
abort() // FIXME: 適宜エラー処理
}
// 遷移先の画面 VC に、選択項目に対応する Item オブジェクトを渡す
itemDetailViewController.item = item
...
}
}
プロパティの初期化処理にクロージャを用いることができる
いくつかの手順を踏んだ結果をプロパティの初期値にしたい場合、クロージャを用いる方法があります。
class Manager
{
static let defaultManager: Manager = {
let manager = Manager()
// ...追加の初期化処理...
return manager
}()
private init() {
...
}
}
static
または class
プロパティの初期化はプロパティ定義部に実装する必要があるため、この方法が有用でしょう。
ちなみにクロージャ内の処理は、初めてそのプロパティが参照されたとき、一回だけ呼び出されます。
省略記法を応用して、いつもの dispatch_async をシンプルに記述する
Swift は関数/メソッドに引数を渡さない場合、()
を省略できます。
またクロージャを実装する場合も、以下のような省略を行うことができます。
- 戻り値が
Void
なら-> Void
は省略可能。 - 引数なしの場合は
() in
は省略可能。 - 関数/メソッドの最後の引数がクロージャである場合、その実装部分は
()
の外に記述することができる。
(Ruby の do 〜 end 構文のような記法)
以下のようなラッパー関数を定義しておきます。
func dispatch_async_in_main(block: dispatch_block_t!) {
dispatch_async(dispatch_get_main_queue(), block)
}
これと省略記法を合わせると、次のような書き方ができます。
// 非 main スレッド内にて...
dispatch_async_in_main {
// ...UI の更新など...
}
Swift3から
DispatchQueue.main.async
でシンプルに書けるようになりました!
Delegate プロトコルを定義するには
Swift のプロトコルは Objective-C と一部仕様が異なるため、Delegate プロトコルを定義する場合は以下の 2 つに注意します。
- 弱参照 (weak) できるよう、
class
を継承する。 - 実装非必須 (@optional) なメソッドは Protocol Extension を用いてデフォルトの実装を行う。
protocol MyMoviePlayerDelegate: class
{
func playerDidEnd(_ player: MyMoviePlayer)
}
extension MyMoviePlayerDelegate
{
func playerDidEnd(_ player: MyMoviePlayer) {
// do nothing...
}
}
デフォルト値が設定されている引数は順不同に渡すことができる (Swift3より廃止)
関数やメソッドの引数にデフォルト値が設定されている場合、その引数は順不同で渡すことができます。
Swift3より、以下のように順不同に引数を渡すことはできなくなりました。
// interface
func listen(
port: Int,
onConnect: Handler = {},
onReceiveData: Handler = {},
onDisconnect: Handler = {}
)
// NG
listen(
1234,
onDisconnect: {
// ... 切断時の処理...
},
onConnect: {
// ... 接続時の処理...
}
)
Optional なコレクションを unwrap して変更を加えても、元のコレクションは変更されない
Array や Dictionary は struct で定義された値型です。
従って、次のようなコードだと、元のコレクションは変更されません。
var optionalArray: [Int]? = [1, 2, 3, 4, 5]
if var array = optionalArray {
array.removeFirst() // optionalArray のコピー array の先頭要素を削除
}
print(optionalArray!) // [1, 2, 3, 4, 5]
上記の例で、配列の先頭要素を削除したいのであれば、optionalArray?.removeFirst()
とする必要があります。
enum 型の Associated Values の活用
enum 型にある Associated Values は、次のような使い方ができます。
何かの処理結果とその値を表現する
enum Result {
case success(Data)
case error(Error)
case cancelled
}
...
// 指定 URL へ GET リクエストする
http.request(.get, url) { (result) in
switch result {
case .success(let data):
print("Success: \(data)")
case .error(let error):
print("Error: \(error)")
case .cancelled:
print("Request cancelled.")
}
}
メタデータのような、様々の型の値を持たせる
enum ExtraData {
case none
case string(String)
case image(UIImage)
case url(URL)
}
...
class Content {
private var extras: [String: ExtraData] = [:]
func insertExtraData(extraData: ExtraData, key: String) {
self.extras[key] = extraData
}
}
...
content.insertExtraData(.string("hoge"), "title")
content.insertExtraData(.image(image), "thumbnail")
ちなみにAssocated Valueの取り出しはif
文でも可能です。
if case .error(let error) = result { // == ではない
print("Error: \(error)")
}
Optional型が持つenum値の応用
Optional型のインターフェースを調べてみると、以下のように定義されていることが分かります。
public enum Optional<Wrapped> : _Reflectable, NilLiteralConvertible {
case none
case some(Wrapped)
...
}
enum値.some
を利用することで、Unwrapされた値を取り出すことができます。
if case .some(let x) = optionalValue {
print(x)
}
switch
文では以下のように応用できます。
リテラルの末尾に?
を付けるシンタックス・シュガーを用いるとよりシンプルに書けます。
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
switch segue.identifier {
case "detail"?:
... // 詳細画面への遷移準備
case "preferences"?:
... // 設定画面への遷移準備
case .some(let identifier):
fatalError("Segue '\(identifier)' not handled.")
case .none:
break
}
}
for文をより賢く使う
Optionalな要素を持つコレクションから、非nilな要素だけを抽出したい場合、for文で以下のように書くことができます。
let values = [1, nil, 3, 4, 5, nil]
for case let value? in values {
print("value = \(value)") // 1, 3, 4, 5だけ出力される
}
特定の型のオブジェクトだけを抽出したい場合は以下のように書きます。
for case let myCell as MyCell in self.tableView.visibleCells() {
myCell.foo() // MyCell型のセルのみ、foo()を適用
}
Optional型のmap, flatMapメソッド
Optional型のmap
やflatMap
メソッドは、非nilの場合だけクロージャが実行され、Unwrapされた値が渡されます。
以下の例は、CollectionType
に条件にマッチした最初の要素を返すmatch
メソッドの拡張です。
extension CollectionType {
func match(@noescape block: (Self.Generator.Element) -> Bool) -> Self.Generator.Element? {
// return self.indexOf(block).map { (index) in
// return self[index]
// }
return self.indexOf(block).map { self[$0] }
}
}
OptionSetでビットフィールドを表現する
C/C++言語ではよく、各ビットが1の値をORして状態を表現するビットフィールドをUInt型などを用いて行っていましたが、SwiftにはOptionSet
プロトコルに準拠した型を用意する方法が良しとされています。
struct Permissions: OptionSet {
let rawValue: Int
static let userExecutable = Permissions(rawValue: 0o400)
static let userWritable = Permissions(rawValue: 0o200)
static let userReadable = Permissions(rawValue: 0o100)
static let groupeExecutable = Permissions(rawValue: 0o040)
static let groupWritable = Permissions(rawValue: 0o020)
static let groupReadable = Permissions(rawValue: 0o010)
static let otherExecutable = Permissions(rawValue: 0o004)
static let otherWritable = Permissions(rawValue: 0o002)
static let otherReadable = Permissions(rawValue: 0o001)
}
let permissions: Permissions = [.userWritable, .userReadable, .groupReadable, .otherReadable] // 644
if permissions.contains([.userReadable, .userWritable]) {
print("User readable & writable.")
}
rawValue
を2進数で指定したい場合は0b00000010
のように記述します。
Float, Double型の剰余演算
Swift3より
%
演算子は廃止され、メソッドが定義されました。
浮動小数点数の剰余を求めたいとき、remainer(dividingBy:)
, truncatingRemainer(dividingBy:)
を使います。
公式ドキュメントに詳しい説明がありますが、
-
remainer(dividingBy:)
は、余りの絶対値が最小となる除算値が用いられる -
truncatingRemainer(dividingBy:)
は、余りが被除数の符号と同じになる
という特徴があります。
let v1 = 7.8
v1.remainder(dividingBy: 2.0) // -0.2
v1.truncatingRemainder(dividingBy: 2.0) // 1.8
Mirror を使ったリフレクション
TODO