情報系人間のブログ

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

気の向くままにブログを書き始める

気の向くままにブログをもう一個作った

oyuk blog

気の向くままに見てください

僕の行動指針

行動指針 Advent Calendar 2016 - Adventar17日目の記事です。

行動指針を書くとなるとなかなか難しいですね。 ということで紹介していきます。

行動指針

自分の常識を疑う

世の中いろんな人がいるので自分の常識と違う人はたくさんいます。そういった人に時には怒りや苛立ちを覚えることもあると思います。そんな時は自分の常識はあくまで自分だけの常識であり、間違っているのは自分ではないかとまず意識するようにしています。常識なんて人の数だけあるので、もはや常識とは?くらいの気持ちです。

反対意見こそ感謝

反対意見は賛成意見より言うのが難しいと思います。人の考えを否定するわけですから簡単には言えないですよね。じゃあなんで反対意見を言うのかというと物事を良い方向に進めるためです。そのために自分の考えに反対してくれるってありがたいと思います。後輩からの反対意見は特に感謝だと思います。先輩に「そうっすね」って言っておけば良いのにわざわざ反対するわけです。自分が先輩のときはそうゆう心構えを持つようにしています。

セルフ・ハンディキャッピングに気をつける

セルフ・ハンディキャッピング - Wikipediaという言葉があります。試験前に「俺勉強してないわ〜」というような「自分○○なんでこれはできないんですよ」と先に逃げ道を作っておくアレのことです。これをやっても何も良いことはありません。苦手なことや難しいことに直面したときは言い訳して逃げ道を作るのではなく、何も言わずにただやれば良いだけです。

おたくになろう

おたくになるほど好きなものがあるって素晴らしいですよね。刹那的な生き方もあると思いますが、好きなものに継続的に情熱を捧げていく方が良いのかなと思っています。

以上です。行動していこう。

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について調べてみます。