34
29

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

中上級者を目指すためのSwift Tips

Last updated at Posted at 2016-02-25

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 nilthrow もできません。
適当な 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 構文のような記法)

以下のようなラッパー関数を定義しておきます。

dispatch_wrapper.swift
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型のmapflatMapメソッドは、非nilの場合だけクロージャが実行され、Unwrapされた値が渡されます。
以下の例は、CollectionTypeに条件にマッチした最初の要素を返すmatchメソッドの拡張です。

CollectionType+match.swift
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プロトコルに準拠した型を用意する方法が良しとされています。

OptionSet.swift
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

To be continued...

34
29
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
34
29

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?