情報系人間のブログ

プログラミング、開発に関することを書いていきます。

Swiftのmapのその次へ

少し前に社内勉強会で発表する機会があったので Swiftのmapからその次へというタイトルでスライドを作りました。

speakerdeck.com

一言で言えばswiftのOptionalやArrayはFunctorだよという話です。 swiftはマルチパラダイム言語と言われており、関数型言語の特徴を取り入れています。 その中の概念の一つにFunctorというものがあり、これをmapを使っている人に知ってほしいその次(の概念)と本発表では呼んでいます。 次はApplicative functorやMonadの発表をしたいのでこのあたりを勉強していきます。

Quickでテストする

Swiftのテストフレームワークとして有名なQuickを使ってみます。

インストール

Carthageに

github "Quick/Nimble"
github "Quick/Quick"

を記述し

carthage update

でインストールできます。CocoaPods,Git submodulesでもインストール可能です。 NimbleはMatcherでQuickで使われています。

テストを書いてみる

簡単な例ですが、こちらのようにRSpec風に書くことができます。

describe("sample"){
    it("1 + 1 = 2"){
        expect(1+1).to(equal(2))
    }
}

非同期処理をテストする場合は以下のように書きます。

describe("API TEST") {
    it("search swift"){
        var isSuccess = false
        
        APIClient.search("swift"){result in
            switch result {
            case .Success:
                isSuccess = true
            case .Error:
                break
            }
        }
        
        expect(isSuccess).toEventually(beTrue(), timeout: 5, pollInterval: 1, description: "")
    }
}

toEventuallyを使用することでタイムアウト時間、pollingの間隔を指定できます。 またwaitUntilを使用する方法もあります。wailUntilはデフォルトのタイムアウト時間が1秒になっています。

 it("search swift2"){
     var isSuccess = false
     
     waitUntil{ done in
         APIClient.search("swift"){result in
             switch result {
             case .Success:
                 isSuccess = true
             case .Error:
                 break
             }
             expect(isSuccess).to(beTrue())
             done()
         }
     }
 }

ReSwiftを使ってみる

ReSwiftというRedux-likeなswiftライブラリがあります。面白そうなので使ってみました。

github.com

ReSwiftの基本概念

ReSwiftではState,Store,Action,Reducerの4つの概念があります。

State

いわゆる状態です。stateはActionによってその状態を変化させる事ができます。

Store

アプリケーション全体のStateを管理します。Actionを受け取るとReducerにそれを渡してStateを更新し、Stateのサブスクライバーに対して変化を通知します。

Action

Stateをどう変化させるかを宣言します。

Reducer

StateとActionから新しいstateを作成します。

試してみる

習うより慣れろで試しに使ってみましょう。Qiitaのapiを使って検索するだけという簡単なアプリを作ります。こちらのサンプルを参考にしています。 完成したものはこんな感じです。コードはこちらにあります。

f:id:reida:20160216235203p:plain

Stateを作る

まずStateを作ってみます。QiitaAPIStateはQiitaのapiを叩いた結果のsarchResultsという状態を持ちます。sarchResultsはqiitaの記事のモデルPostの配列となっています。Result型になっていますが以降無視します。

struct QiitaAPIState {
    var sarchResults: Result<[Post]>?
}

protocol HasQiitaAPIState {
    var qiitaAPIState: QiitaAPIState { get set }
}

AppStateはアプリ全体のStateを実装します。先ほどのHasQiitaAPIStateをここで適合させます。アプリが他にもStateを持つ場合はそれぞれStateを作ってAppStateに実装していきます。

struct AppState: StateType,HasNavigationState,HasQiitaAPIState,HasQiitaSceneState {
    var navigationState = NavigationState()
    var qiitaAPIState = QiitaAPIState()
    var qiitaSceneState = SearchQiitaScene.State()
    
    init() {

    }
}

Actionを作る

QiitaAPIStateをどのように変化させるかというActionを作成します。ここではPostがどう変わるかを宣言します。

struct SetPostSearchResult:Action {
    let result:Result<[Post]>
    init(_ result:Result<[Post]>) {
        self.result = result
    }
}

Reducerを作る

ReducerはActionのSetPostSearchResultを使ってStateのQiitaAPIStateを変化させます。handleAction内でActionがSetPostSearchResultであればsetPostSearchResultが実行されます。ここでsarchResultsが更新されます。

struct QiitaAPIReducer:Reducer{
    
    typealias ReducerStateType = HasQiitaAPIState
    
    func handleAction(state: ReducerStateType, action: Action) -> ReducerStateType {
        switch action {
        case let action as SetPostSearchResult:
            return setPostSearchResult(state, result: action.result)
        default:
            return state
        }
    }
    
    func setPostSearchResult(var state:ReducerStateType,result:Result<[Post]>)->ReducerStateType {
        state.qiitaAPIState.sarchResults = result
        return state
    }

}

Storeを作る

StoreはReducerとStateを保持します。先ほど作成したAppState,QiitaAPIReducerが初期化時に渡されています。Storeはアプリに一つだけ?になるようです。

let mainStore:MainStore = MainStore(reducer:CombinedReducer([NavigationReducer(),QiitaAPIReducer(),SearchQiitaScene._Reducer()]), appState: AppState(), middleware: [loggingMiddleware])

その他部品を作る

これでRedux的な部分は作成しましたのでこれらをUIViewController等の慣れ親しんだ所に流し込んでいきます。Stateの変化の通知を受け取るためにStoreSubscriberに適合させ、subscribeします。

class ViewController:UIViewController,StoreSubscriber {

    let store:Store = mainStore

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        store.subscribe(self)
    }
}

subscribeするとnewStateでStateの変化を受け取ることができます。

    func newState(state:AppState){
        if let searchResults = state.qiitaAPIState.sarchResults {
            switch searchResults {
            case .Success(let posts):
                self.posts = posts
                self.tableView.reloadData()
            case .Error(let error):
                print(error)
            }
        }
    }

最後にActionを発行する関数searchPostsを追加します。ActionCreatorはその名の通りActionを作成するための処理を記述します。 検索結果が返ってくるとstore.dispatch()でstoreへActionを渡します。ここでは検索結果の配列です。正常に動作してれば配列に検索結果の要素が入っているでしょう。 storeはこれを受け取るとReducerへStateとActionを渡します。この時点のStateは以前の検索結果です。 ReducerはActionで宣言された変化をStateへ適用し新たなStateを発行します。つまり以前の検索結果を新たな検索結果に置き換える事になります。 この新たなStateがサブスクライバーに通知されます。これが先ほどのnewState()です。

struct QiitaAPIActionCreator{
    
    func searchPosts(query:String)->ActionCreator {
        return {state,store in
            if query.isEmpty {
                store.dispatch(SetPostSearchResult(Result(value: [])))
                return nil
            }

            APIClient.search(query){result in
                store.dispatch(SetPostSearchResult(result))
            }
            
            return nil
        }
    }
}

最後に

Reduxという概念が何かもわからないまま使ってみました。 気になる点といえばstateが更新されると全てのサブスクライバーnewState()が実行される点でしょうか。 他にはStateが増えると複数のStoreが必要になるのか?という疑問がありますが、まずはReduxについて調べてみます。

Terminalから翻訳しよう

変数名やメソッド名をどうしようかと悩んでgoogle翻訳等を使うことがあると思います。
ですがブラウザに移動するのは面倒です。ターミナルで翻訳できないかと思い、探したところ便利なものを見つけました。

それがこちらです。

soimort/translate-shell · GitHub

Homebrewでインストールできます。

brew install https://www.soimort.org/translate-shell/translate-shell.rb

使い方は簡単でtransコマンドの後に翻訳したい言葉を入力するだけです。

$ trans 翻訳
翻訳
(Hon'yaku)

translation

Definitions of 翻訳
[ 日本語 -> English ]

noun
    translation
        翻訳, 訳書, 翻訳物, トランスレーション
    deciphering
        解読, 翻訳

英語から日本語にするには以下のようにtrans :jaとすればokです。

$ trans :ja translation
translation
/transˈlāSHən,tranz-/

翻訳
(Hon'yaku)

Definitions of translation
[ English -> 日本語 ]

noun
    翻訳
        translation, deciphering
    訳書
        translation
    翻訳物
        translation
    トランスレーション

2015年振り返り

2015年を振り返る。
ぱっと思いつくトピックとそれについて一言をまとめる。

プログラミング

アプリを4つリリース

iosアプリが3つ、androidアプリが1つ。
ダウンロード数はとても少ないけどリリースしたことに価値があると思っておこう。

バックエンドを少し勉強

アルバイトで少しだけcakephpを触った。
今までほぼスマホアプリだけだったのでこの知見を次につなげていく。

学生として

インターン

就活関係でインターンに行った。二社をそれぞれ一週間という短い期間だったけど単純に面白かったし会社の雰囲気を知れて良かった。
選考は落ちたのでまだまだ能力不足である。

就活

就職先がきまった。来年4月からは社会人としてがんばるぞい。

研究

自分の興味あることを深く調べるという事がどれほど楽しく大変かよく分かった。
知れば知るほど分からない事が増えていく。

プライベート

家庭教師

中学生に数学を教えた。約10歳下の子とたくさん話してジェネレーションギャップを感じた。
身内ではない中学生とがっつり話す機会はこれからはなさそう。純粋に楽しかった。

旅行

京都大阪に一人でいった。
自由に行動できる身軽さはありがたいが、感情を共有できないことは物足りないか。
伏見稲荷大社の千本鳥居に夜行った。怖い。これに尽きる。

ビジネスホテル

いろいろあって宿泊する機会が多かった。
ビジネスホテル12件、カプセルホテル3件で日数で言うと一ヶ月ほど。
やはり家が落ち着くという結論に達した。

来年は

  • アウトプットしていく。
  • 外出する。