yashiganiの英傑になるまで死ねない日記

週末はマスターバイクでハイラルを走り回ります

SwiftでNS_OPTIONSスタイルのEnumを実現する

Swiftenumは便利ですが,たまには使い慣れたNS_OPTIONSスタイルのenumを作りたくなるときがあります. 素朴にやるとこのように実装できます.

/// My Options
enum Options: UInt {
  case None = 0
  case A    = (1 << 0)
  case B    = (1 << 1)
}

しかし,これでは全ての組み合わせのパターンをcaseとして用意する必要がありますし,用意したところで常にrawValueでの比較が必要です.

// My Options doesn't have it!
let options: Options = Options(rawValue: .A.rawValue | .B.rawValue)

こんなのはとてもじゃないけど使えません.

UIViewAutoresizingの実装を見てみる

UIKitにはNS_OPTIONSを使ったenumがいくつもあり,それらはSwiftでもObjective-Cのときと同じように使うことができます. 実際にはどう実装されているのでしょうか? 代表的なNS_OPTIONSを使ったenumである,UIViewAutoresizingを例に実装を見てみます.

struct UIViewAutoresizing : RawOptionSetType {
    init(_ rawValue: UInt)
    init(rawValue: UInt)

    static var None: UIViewAutoresizing { get }
    static var FlexibleLeftMargin: UIViewAutoresizing { get }
    static var FlexibleWidth: UIViewAutoresizing { get }
    static var FlexibleRightMargin: UIViewAutoresizing { get }
    static var FlexibleTopMargin: UIViewAutoresizing { get }
    static var FlexibleHeight: UIViewAutoresizing { get }
    static var FlexibleBottomMargin: UIViewAutoresizing { get }
}

素朴にenumで実装するわけではなく,structで実装されています. 通常Swiftenumcaseで各値を決めますが,全てstaticpropertyとして定義されているのがおもしろいです.

Objective-Cのときと同じように使える秘密は,RawOptionSetTypeにあってこのような継承関係を持っています.

RawOptionSetType
       |
       |----------------------------------------------
       |                      |                      |
_RawOptionSetType   BitwiseOperationsType   NilLiteralConvertible
       |
       |----------------
       |               |
RawRepresentable   Equatable

これらを実装するとこうなります.

/// My Options
struct Options : RawOptionSetType {
    typealias RawValue = UInt
    private var value: UInt = 0
    init(_ value: UInt) { self.value = value }
    init(rawValue value: UInt) { self.value = value }
    init(nilLiteral: ()) { self.value = 0 }
    static var allZeros: Options { return self(0) }
    static func fromMask(raw: UInt) -> Options { return self(raw) }
    var rawValue: UInt { return self.value }

    static var None: Options { return self(0) }
    static var A: Options { return Options(1 << 0) }
    static var B: Options { return Options(1 << 1) }
}

これでUIViewAutoresizingと同じように使えます.

let options: Options = .A | .B

大変な実装をすることでようやくNS_OPTIONS相当のstructを手に入れることができました. 深淵なるSwiftのテクニックに触れることができて,大満足ですね.

お得情報コーナー

こんなに実装してらんねえ!と思ったアナタに朗報です! Bridging Headerにこのように書いてみましょう.

typedef NS_OPTIONS(Options, NSUInteger) {
  OptionsNone,
  OptionsA,
  OptionsB,
};

これで大変な実装なしにSwiftにexportされます. お手軽ですね!

pure Swiftで実装したいというアナタにはこれがオススメです.

このジェネレータを使えばメッチャ簡単にNS_OPTIONS相当のstructの実装が作れます.

参考

RawOptionSetTypeについての解説です. かなり初期に書かれたものなので,現在は微妙に仕様が変わっていますが参考になるとおもいます.

Hatena Engineer Seminar #4で「はてなのiOSアプリとSwift」という発表をしました #hatenatech

2/7に開催されたHatena Engineer Seminar #4 @Tokyoで「はてなiOSアプリとSwift」という発表をしてきました.

現在,はてなiOSアプリ開発ではSwiftを標準的に採用しており,その経験を紹介した形です. 発表スライドはこちらから御覧ください.

Swift with apps by Hatena // Speaker Deck

補足

例に使っているコードについて

例としてあげたSwiftのコード例は冗長なものですが,型推論できずコンパイルできなかったので意図に助長な実装にしています. 推論がキチンと働けばsortedに与えるclosureの引数は省略することができます.

func latestHotentry(bookmarks bs: [Bookmark]) -> Bookmark? {
// 仕様どおりに型推論されればこれでよい
    return bs.filter { $0.isHotentry }
             .sorted { $0.bookmarked.compare($1.bookmarked) == .OrderedDescending }.first
}

スライドでも言及していますが,主にclosureまわりは推論が弱い印象があります.

Objective-Cの例について,改行を盛っているという意見があるかもしれません. その意見は全くもって正しくて,スライドの都合上可読性のため意味的には無駄な改行を入れています. しかし,実際に実装するにあたっても可読性のために同じようなところに改行を入れるとおもいます.

懇親会で質問されたこと

覚えている範囲ですが,懇親会で質問されたことを紹介します.

はてなにはモバイルアプリのエンジニアは何人くらいいるのか

現状アプリ開発を担当しているのは4人です. ちなみに現在開発に関わってはいませんが,スキルを持っているエンジニアはあと3人いるので,スマート会は7人で構成された組織です.

ReactiveCocoaは使っていないのか

使っていません. ReactiveCocoaはかなり大きく複雑なライブラリですし,実装を大きく変えてしまいますので,依存があまりに大きいです. iOSアプリ開発は毎年変化がかなり激しいので,できるだけ依存は減らしたいのであえて採用していません. 一方で,スライドで紹介したMantleはやっていることが小さい(JSONからモデルへのマッピング)ので取り替えが難しくありません. そういうものは積極的に採用しています.

感想

長時間なだけでなく,発表の内容も幅広くバラエティに富んでいたので参加者のみなさんはさぞ疲れただろうとおもいます. にも関わらず,最後まで真剣に聞いていただきありがとうございました!

追記

Twitter経由で質問をもらったので追記します.

このようにHTTPのレスポンスを抽象化して,genericsで解決すると楽です.

https://github.com/cockscomb/Swift-APIClient-Sample/blob/060ba8e9cb4e9924a071d7b3adf533bbb2832bc3/APIClient/APIClient.swift#L60-L78

この戦略については,「はてなエンジニアアドベントカレンダー2014」でid:cockscombが詳しく紹介しています.

switchとType Castingで安全なprepareForSegueを実装する

画面遷移時に実行されるprepareForSegue:sender:ですが,Objective-Cで実装するといいかんじに不安な実装が完成します.

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqual:@"HogeSegue"]) {
        HogeViewController *vc = (HogeViewController *)segue.destinationViewController;
        // 依存を注入
    }
    else if ([segue.identifier isEqual:@"FugaSegue"]) {
        UINavigationController *nc = (UINavigationController *)segue.destinationViewController;
        FugaViewController *vc = (FugaViewController *)nc.childViewControllers.firstObject
        // 依存を注入
    }
}

愚直にコンバートもできますが,SwiftではswitchとType Castingを組み合わせて実装してみます.

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    switch segue.destinationViewController {
    case let vc as HogeViewController:
        // 依存の注入
    case let nc as UINavigationController:
        switch nc.childViewControllers.first {
        case let vc as FugaViewController:
            // 依存の注入
        default: ()
        }
    default: ()
    }
}

Objective-Cで実装するときは,いつもUINavigationControllerから子のView Controllerを取り出す実装がモヤモヤしていたのですが,switchとType Castingを組み合わせると少しいいかんじにまとまります. UIStoryboardSegueを使った画面遷移の実装はどうしても不安になりがちですが,これくらい落ち着いた実装だと少し安心できます.

もちろん分岐が無いときは,if letするのもアリです.

if let vc = (segue.destinationViewController as? UINavigationController).childViewControllers.first as FugaViewController) {
    // 長い...
}

参考

Swiftでマクロっぽいことをする

Objective-Cだと今でもマクロを使うことはあります. (ReactiveCocoaなんかも依存していますね.) プロダクトコードで使うのはできたら避けたいマクロですが,ビルド設定によって動作を切り替えたいときとかには結構便利です. しかしながら,Swiftにはマクロは存在しないので,このような手段を使って代替します.

Simple Macros

いわゆる定数を定義するマクロです. Swiftで定数を定義したいときはletを使うべきです.

#define FADE_ANIMATION_DURATION 0.35

このようなマクロによる定数の定義は,

let FADE_ANIMATION_DURATION = 0.35

letを使えば実現できます. マクロに比べ型チェックがされますので圧倒的にセキュアです.

筆者はこの他に定数を定義するのにcomputed propertyをよく利用します. structureやenum,グローバルコンテキストでも使えて便利なのでオススメです(特にenumで使うのがかっこいいです).

Complex Macros

例えば,MINMAXのような関数のように働くマクロはSwiftにはエクスポートされません. このタイプのmacroはCやObjective-Cでは型付けを回避するために使われますが,デバッグリファクタリングが非常に難しいです. Swiftではジェネリックタイプもありますので普通に関数で表現してください.

ちなみに,MINMAX相当の関数はこのように定義されています.

func min<T : Comparable>(x: T, y: T) -> T
func max<T : Comparable>(x: T, y: T) -> T

Build Configurations

ビルド時の設定によって動作を変更するときに使います. 以下のものがデフォルトで用意されています.

Function Valid arguments
os() OSX, iOS
arch() x86_64, arm, arm64, i386

例えば,iPhoneシミュレータのみで実行したいコードはこのように書けます.

#if os(iOS) && arch(i386)
// do something
#endif

よくあるチーム内配布のビルドでのみ実行したいなどの場合は,Other Swift Flagsを使ってください. Preprocessor Macrosに設定してもSwiftのコードでは参照できないので TARGETS > Build Settingsから-DBETATESTINGのように指定し,このように使います.

#if BETATESTING
// do something
#endif

まとめ

従来マクロでやっているようなことをSwiftで実現する代替テクニックを紹介しました. 特にOther Swift Flagsはよくなんだったっけ?となるので覚えておくとよいでしょう.

参考

CocoaPodsのPrivate Specsを作るときにしておきたいちょっとした工夫

外部に公開できないライブラリが増えてくるとPrivate Specsが欲しくなります. Podfileを丁寧に書けば依存関係を解決できますが,Private Specsを使うと依存関係の解決をCocoaPodsに任せることができるので,podspecを利用する際やPrivateなpodspecに依存したpodspecを開発する際に便利です. なお,Private Specsを作ることに関しては以下のエントリが大変よくまとまっていますので参考になります.

基本的にこのエントリ通りやれば間違いはありません.

ここでは"リポジトリはそのまま使えばいい"と紹介されていますが,pod repoコマンドはSpecsディレクトリが存在するとそれ以下にpodspecを格納してくれますので,master repoのようにSpecsディレクトリを追加しておくとよいです. 上記のエントリの例のように,リポジトリを作ってそのままpod repo pushするとトップレベルの階層にspecが格納されます.

たいていの場合,READMEに簡単な使い方を記載すると思います. ライブラリが多くなってくるとGHEなどでREADMEが少し見にくくなってしまいますので,master repoのような構成にするのがオススメです(この間失敗した).

参考