Swift で ReactiveCocoa を使う

はじめに

SwiftiOS アプリを実装し直すにあたって、 ただ Swift に翻訳するだけではつまらないので、 MVVM アーキテクチャで実装することに決めた。

View/ViewController と ViewModel のバインド、 イベントや非同期 API 呼び出しのインタフェースを統一するために、 ReactiveCocoa を導入する。

CocoaPods で RactiveCocoa をインストール

Podfile に

pod "ReactiveCocoa"

を追加して pod install

Bridging-Header で ReactiveCocoa のヘッダーファイルをインポート

<プロジェクト名>-Bridging-Header.h に次の行を追加。

#import <ReactiveCocoa/ReactiveCocoa.h>

これで Swift から ReactiveCocoa が使えるようになった。

RACSignal を作成してみる

API 非同期呼び出しのインタフェースを RACSignal で統一したいので、RACSignal でラップする。

class CommentViewModel: NSObject {
    var ownerName: String!
    var repoName: String!
    var issueId: Int!
    var comment: String?

    func postComment() -> RACSignal {
        return RACSignal.createSignal({ (subscriber) -> RACDisposable! in
            let manager = AFHTTPRequestOperationManager.manager()
            let operation = manager.POST(
                "/repositories/¥(self.ownerName)/¥(self.repoName)/issues/¥(self.issueId)/comments",
                parameters: ["comment": self.comment],
                success: { (operation, response) -> Void in
                    subscriber.sendNext(response)
                    subscriber.sendCompleted()
                },
                failure: { (operation, error) -> Void in
                    subscriber.sendError(error)
                }
            )
            return RACDisposable(block: { () -> Void in
                operation.cancel()
            })
        })
    }
}

UITextView に ViewModel をバインドしてみる

UITextView の text に変更があったら即座に ViewModel に反映させてみる。 Objective-C では RAC マクロを使ってバインドできるけど、Swift では RAC マクロが使えないので、 低レイヤーのメソッドを使うしかなかった。

self.commentTextView.rac_textSignal().setKeyPath("comment", onObject: self.viewModel)

ちなみに、同じことが UITextField でも可能。

ViewModel のプロパティを UIBarButtonItem にバインドしてみる

例えば、「ViewModel の comment プロパティが空ではないとき UIBarButtonItem を有効にする」 というのを ReactiveCocoa で実装するとこんな感じ。

self.viewModel.rac_valuesForKeyPath(
    "comment",
    observer: self.viewModel
).map({ (next) -> AnyObject! in
    let newComment = next as String
    return !newComment.isEmpty
}).setKeyPath("enabled", onObject: self.postButtonItem)

RACCommand を使って ViewModel のメソッドも UIBarButtonItem にバインドしてみる

「ViewModel の comment プロパティが空ではないとき UIBarButtonItem を有効にする」だけでなく、 UIBarButtonItem をタップしたときのアクションも併せて ReactiveCocoa で実装。

let commentSignal = self.viewModel.rac_valuesForKeyPath(
    "comment",
    observer: self.viewModel
).map({ (next) -> AnyObject! in
    let newComment = next as String
    return !newComment.isEmpty
})

let postCommand = RACCommand(
    enabled: commentSignal,
    signalBlock: { (input) -> RACSignal! in
        return self.viewModel.postComment()
    }
)

self.postButtonItem.rac_command = postCommand

ReactiveCocoa を使ってみて

インタフェースを RACSignal に統一できるので、コードを書きやすい。 特に RACSignal 化した非同期呼び出しは、引数でコールバックを渡す必要がないのでスッキリする。

ただ、コントロールにバインドする RACSignal や RACCommand は、 ViewModel のプロパティとして実装したほうが良かった。

ReactiveCocoa 難しい。 RACSignal にようやく慣れてきたけど、 RACCommand やその他の機能はまだ理解が追いついていない。

Bootstrap の Tooltip で改行する方法

title 属性に <br> タグを含んだ文字列、data-html 属性に true を設定すればいい。

<span rel="tooltip"
  data-toggle="tooltip"
  data-placement="right"
  data-html="true"
  title="1行目<br/>2行目">
  コンテンツ
</span>

Rails なら simple_format を使うと便利。

<span rel="tooltip"
  data-toggle="tooltip"
  data-placement="right"
  data-html="true"
  title="<%= simple_format(item.tooltip) %>">
  <%= item.content %>
</span>

Swift で AFNetworking を使う

CocoaPods で AFNetworking をインストール

Podfile に

pod "AFNetworking"

を追加して pod install 実行。

Bridging-Header で AFNetworking のヘッダーをインポート

<プロジェクト名>-Bridging-Header.h に

#import <AFNetworking/AFNetworking.h>
#import <AFNetworking/UIImageView+AFNetworking.h>

を追加。

画像の非同期ダウンロード機能も使いたいので、 AFNetworking.h だけでなく UIImageView+AFNetworking.h もインポートしている。

AFHTTPRequestOperationManger を使ってみる

func loadRepositories() {
    let manager = self.createManager()
    manager.GET(
        "user/repositories/",
        parameters: nil,
        success: { (operation, json) -> Void in
            let array = json as NSArray

            // 配列を使ってなにかやる
        },
        failure: { (operation, error) -> Void in
            // エラー処理
        }
    )
    return nil
}

private func createManager() -> AFHTTPRequestOperationManager {
    let manager = AFHTTPRequestOperationManager(baseURL: self.baseUrl)

    manager.requestSerializer = AFHTTPRequestSerializer()

    // ユーザー名とパスワードを設定
    manager.requestSerializer.setAuthorizationHeaderFieldWithUsername(
        self.username!, password: self.password!)

    return manager
}

画像を非同期にダウンロードして表示してみる

UIImageView+AFNetworking.h をインポートすると、 AFNetworking が提供する画像の非同期ダウンロードが使える。

cell.imageView.setImageWithURL(
    NSURL(string: imageUrl),
    placeholderImage: userMenuImage
)

Swift で MagicalRecord を使う手順メモ

CocoaPods で MagicalRecord をインストール

Podfile に

pod "MagicalRecord"

を追加し

pod install

でインストール。

<アプリ名>-Bridging-Header.h に

#import <MagicalRecord/CoreData+MagicalRecord.h>

を記述して、Swift から MagicalRecord を使えるようにする。

なお、Bridging-Header を作る手順は先日の記事で書いた。

xcdatamodeld を作成

プロジェクト作成時に CoreData を使う選択をしていれば、 <プロジェクト名>.xcdatamodeld ファイルが作成されているので、 Xcode でエンティティを定義する。 その際、エンティティに対応する NSManagedObject サブクラスも指定。

f:id:griefworker:20141023000747p:plain

NSManagedObject のサブクラスを作成

プロジェクトに NSManagedObject のサブクラスを追加する。 先ほど作成したエンティティに対応するクラスを Swift で生成。

import Foundation
import CoreData

@objc(Favorite)
class Favorite: NSManagedObject {

    @NSManaged var login: String
    @NSManaged var owner: String
    @NSManaged var name: String
    @NSManaged var slug: String
    @NSManaged var createdAt: NSDate
    @NSManaged var updatedAt: NSDate
    @NSManaged var order: NSNumber

}

CoreData スタックのセットアップ

MagicalRecord を使って CoreData を初期化するコードを AppDelegate に記述する。 Xcode が生成した CoreData 関連のひな形は削除。

import UIKit
import CoreData

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate {
    var window: UIWindow?

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // CoreData スタックを初期化
        MagicalRecord.setupCoreDataStackWithStoreNamed("Bitissues.sqlite")

        // ...省略...

        return true
    }

    // ...省略...
}

これで準備完了

あとは Objective-C のときと同様に MagicalRecord が使える。 試しに、NSFetchedResultsController を取得してみるコードがこちら。

import Foundation
import CoreData

extension Favorite {
    class func fetchAllWithLogin(login: String, delegate: NSFetchedResultsControllerDelegate?) -> NSFetchedResultsController {
        let predicate = NSPredicate(format: "login = %@", login)
        return Favorite.MR_fetchAllSortedBy(
            "order",
            ascending: true,
            withPredicate: predicate,
            groupBy: nil,
            delegate: delegate
        )
    }
}

Swift で CocoaPods を使う

RubyMotion で作ったアプリを Swift で作り直すことにした。 手始めに、Swift でも CocoaPods のライブラリを使えるようにしてみる。

CocoaPods でライブラリをインストール

Swift プロジェクトのルートディレクトリで

pod init

を実行し Podfile を生成。

Podfile には、試しに

pod "AFNetworking"

を追加してみる。

pod install

を実行すればライブラリがインストールされ、Podfile.lock と .xcworkspace ファイルが出力される。

Bridging-Header.h を作成

.xcworkspace ファイルを Xcode で開いて、 <プロジェクト名>-Bridging-Header.h ファイルをプロジェクトに追加する。

// 例:SwiftSample-Bridging-Header.h

#import <AFNetworking/AFNetworking.h>

Build Settings でファイルを設定。

f:id:griefworker:20141019221329p:plain

これで Swift から CocoaPods のライブラリが使えるようになった

ちょっと試してみる。

f:id:griefworker:20141019221344p:plain

補完候補にちゃんと AFNetworking のクラスが表示された。 AFNetworking のクラスを使っても、ちゃんとビルドが通る。

Vim script テクニックバイブル

自分は .vimrc に設定を記述する程度の、ドが付く Vim script 初心者。 Vim プラグインは使うのみで、書いたことも無ければ、ソースコードもほとんど読んでない。 だけど、Vim を使っているからには、Vim script を書けるようになった方がいいな、 とは思っていた。

そんな中、Vim script だけを扱ったマニアックな書籍がついに発売され、 ようやく重い腰を上げた。 テクニックバイブルという名前が付いていて、300ページ超あるけど、 内容は Vim script 初心者向け。 文字が大きいので、プログラミング経験がある人ならスラスラ読める。

本書の構成はざっと

  • Vim script の基本的な構文と Vim での使い方
  • Vim プラグイン開発実践
  • Vim 組み込み関数リファレンス、
  • デバッグ手法やライブラリといった実践テクニック

となっていて、Vim script 入門書であり、クックブックでもあり、API リファレンスでもある。 1 冊で 3 度おいしい。

中でも一番の注目は、Vim プラグイン開発の章。 実はこれが目的で購入してたりする。 「保存時に自動的に行末の空白スペースを削除するプラグイン」を題材に

  • ユーザー設定による副作用を回避する定型コード
  • 関数でプラグインの機能を実装
  • Exコマンドを実装
  • キーマッピングを実装
  • オプション変数を用意
  • ヘルプを作成

といった、Vim プラグインを作って公開するまでの一連の流れが掴めた。

実践テクニックの章では、Vim にデバッガ機能が搭載されていて、 ブレークポイントをセットできることを知れたのが大きい。 :echomesg を使ったプログラマお馴染みプリントデバッグでやるしかないと、 勝手に思い込んでいた。 デバッグが捗るに違いない。

本書を読めば、簡単なプラグインを書けるところまではたどり着ける。 実用的なプラグインを書くためには、そこからさらに勉強が必要。 公開されている Vim プラグインソースコードを読むのが一番良さそうだ。

まだアイデアが無いけど、Vim プラグインを書いてみたくなった。 利用している Vim プラグインソースコードを手本に勉強して、 アイデアが閃いたときに備えるとしよう。

Vim scriptテクニックバイブル?Vim使いの魔法の杖

Vim scriptテクニックバイブル?Vim使いの魔法の杖

PAUL

天神で買い物した帰りに小腹が空いたので、 三越地下 2F にある PAUL でパンを買ってみた。 そういえば PAUL でパン買うの初めてかも。

f:id:griefworker:20141011153426j:plain

定番のクロワッサン。 ちょうど焼き立てを買えたので、家についてもまだ温かかった。 外はパリッ、中はもっちり。 バターの風味が程良い。 生地が甘く感じた。

f:id:griefworker:20141011153449j:plain

こちらも定番のパン・オ・ショコラ。 チョコチップがややビターで、カフェオレによく合う。

f:id:griefworker:20141011153527j:plain

3つめのクイニーアマンは結構甘かった。 デザートやおやつに丁度いい。

パンのサイズはメゾンカイザーの方が大きくて食べ応えあったけど、 クロワッサンの味は PAUL の方が好みだな。

関連ランキング:パン | 西鉄福岡駅(天神)天神南駅天神駅