「目標」という言葉の本当の意味

彼女が住む小田原からの帰りの電車。およそ1.5時間。

暇だったので、Kindle Unlimitedで本を読むことにした。

Kindle Unlimitedで読める本を漁っていると、こんな本を見つけた。

リーダーの才能ゼロどころかマイナスの僕がコーチングなど無理だと思いスルーしかけた。 が、「目標の達人ノート」というサブタイトルが目に入り、これは自分自身のコーチングをする本だと気づく。

僕は常に自分自身に甘く、自分自身のコーチング力が必要。 かつ、目標や夢を何度も立てているが何か物足りない感触があり、読んでみることにした。

電車の中で25%ほどくらい読んで、その時点でとても大事なことが書かれているように思ったので現時点での要点をまとめておく。

「目標」「ゴール」「目的」「ビジョン」「夢」の本当の意味

全部だいたい同じモノだと思っていた。

でもどれも違った。

ちゃんと意味を知ればしっかりとした目標をたてられるようになりそう。

目的

成し遂げようと目指す事柄

目標

目的を達成するために設けた目当て

つまり、 目標はあくまで目的に向けての目印でしか無い。

ゴール

競技などで着順の決まる一番最後の地点・決勝点

つまり、 目的のための最終的な目印。

将来実現させたいと、心の中に描いている願い

純粋に実現したいと思うシンプルなことでいい。些細なことでもよい。

ビジョン

ゴールを手にした瞬間に見る光景

  • それを今想像する。そして、そのインパクトの瞬間に得られる感情を味わう
  • ゴールに向けて行動するための強化剤
  • 鮮明でありありとしていて、その光景を頭のなかで見るだけで、嬉しいとか、楽しいとか、ドキドキワクワクするとか、そういった感情が湧くものでなくてはならない
  • 目標の達人たちは、そういうビジョンをいつも描いて想像しては自分のエネルギーにしている
  • 人はイメージを記憶できないから、ちょくちょく描く必要がある. 一度描いただけでは時間の経過とともに効果がどんどんなくなる. できれば毎日、毎週、毎月、毎年、ビジョンを描いて想像し続ける

ドリームリスト (バケツリスト)

夢のリスト

  • 単純な願望
  • 叶ったら儲けもんくらいの気軽さでいい
  • 100個くらい書く
  • どんな些細なことでもいいから自由に願望をリストアップする

ドリームツリー

ドリームリストのなかで「何が何でも実現したいと思うこと」をピックアップし、その夢に日付を付けてゴールとし、目的と目標をつけてゴールとし、目的と目標を決めたもの

つまり、これら全部をまとめた図のようなもの。

ちゃんと書こうとか、うまく書こうとか思わなくていい。何回も書き直すことでどんどん言葉が洗練されていく。曲を創作したり、彫刻を作る作業と同じ。最初は荒削りでも全体像を描き、その上で徐々に細部を作り込んでいけばいい。

つまり図にするとこう

f:id:taku_oka:20170813191648p:plain

例: マラソン選手の場合

「マラソン選手の場合、ただオリンピックで金メダルを取ると言うのではなく、目的とゴールをセットにした言葉を自分に聞かせればいいんです。 『私は、自分が可能性に挑戦することで、子どもたちに夢を持つことの素晴らしさを伝えたいんです。そのために、次のオリンピックで金メダルに挑戦します。だから毎日努力して、今年の選考レースで優勝します』とね。 そして、具体的で確実な一つの行動を、目標という指標を立てながら続ける。これが、効果的にゴールや目標を活用する方法です」 「少しわかってきたような気がします。この目的、ゴール、目標の三つがそろって初めて、目標を立てることの効果が生まれるんですね。」

目的➜「自分が可能性に挑戦することで、子どもたちに夢を持つことの素晴らしさを伝えたい」

ゴール➜「次のオリンピックで金メダル」

目標➜「今年の選考レースで優勝」

まとめ

どんな些細なことでもいいので や願望をリストアップする。

その夢の中から本当に心から実現したい夢をいくつか選ぶ。

どうしてそれを手にしたいのか、いつまでにどんな状態になりたいのかといった、 目的ゴール をはっきりさせる。

次に、そのゴールを手にするために具体的な目印や通過点といった 目標 を設定する。

さらにゴールに向かう行動を促進させるために、ゴールを手にした瞬間の ビジョン や上手くいったときのイメージを繰り返し心のなかで描く。そのときどんな感情に自分が惹きつけられるのかを明確にしておく。

それぞれが単体ではイマイチだが、全てが揃うと効果が出る。

夢やビジョンや目的といった抽象度が高く主観的な事柄で右脳を刺激し、さらに現実的な計画を示す客観的な目標で左脳を刺激して両方の脳を使うことで感情と理性が同時に成功へ導く。

これを実現するためのツール

夢/ビジョン/ゴール/目的/目標をうまいこと定義できるツールはどれだろう?

なかなか複雑になるので簡単にはできそうになさそう。

夢のリストがあって、その中からいくつかピックアップしてゴールを設定して、それは在る目的に紐付いていて、ビジョンもあって、そのしたに目標がたくさんあって…という複雑さがある。

考えた候補

  • Trello, Things, WunderList ➜ ❌ 複雑さに耐えられない
  • Markdown ➜ ❌ 複雑さに耐えられない
  • MindNode ➜ ⭕ いける
  • Sketch ➜ 💦 大変すぎ
  • 手書き ➜ 💦 大変すぎ

結果、自分はMindNodeでやってみることにした。

さいごに

やべぇ、クソ意識が高い記事ができた。恥ずかしいけどきっと誰も見てないだろう。

でもとても大事なこと。

夢やゴールを再設定するのはまだこれからなので頑張ります。

WKWebViewで正しくPullToRefreshする

WebViewでUIRefreshControlを使用してPullToRefreshする際、少しだけ工夫しなければ汚い挙動になってしまったので覚え書き。

というより、そもそもUIRefreshControlの使い方が間違っていた。

悪い例

f:id:taku_oka:20170811185655g:plain

間違った実装で不快な動きをしてしまっていた。 ユーザーが指を離すまえにリロードしてしまうと表示が崩れる。

コード

import UIKit
import WebKit

class BadExampleViewController: UIViewController {

    let webview = WKWebView()
    let refreshControl = UIRefreshControl()

    override func viewDidLoad() {
        super.viewDidLoad()

        webview.frame = view.frame
        view.addSubview(webview)

        webview.scrollView.refreshControl = refreshControl
        refreshControl.addTarget(self, action: #selector(self.didPullToRefresh(sender:)), for: .valueChanged)

        webview.load(URLRequest(url: URL(string: "https://www.apple.com/")!))
    }

    func didPullToRefresh(sender: UIRefreshControl) {
        refreshControl.endRefreshing()
        webview.reload()
    }
}

そもそも引っ張ってる途中のタイミングでローディングをするのはタイミングとして適切ではなかった。 特にWebViewはドラッグ中にリロードしてしまうと表示がガタつくのでいけない。 本来ローディングを開始すべきタイミングはユーザーが指を離した時だった。(恥ずかしい)

そもそもUIRefreshControlの正しい使い方とは

ScrollViewに追加

if #available(iOS 10.0, *) {
    scrollView.refreshControl = refreshControl
} else {
    scrollView.addSubview(refreshControl)
}

引っ張ったタイミングのイベントを受け取る

addTarget メソッドを使って関数を登録しておきます。

ローディングを開始したことを表すアニメーションを開始

beginRefreshing() を呼ぶ。 ローディングしている間、ScrollViewの上部に隙間があき、そこでUIRefreshControlがアニメーションし続けます。

ローディングが終了したことを表すアニメーションを開始

endRefreshing() を呼ぶ。 ScrollViewの上部に隙間でアニメーションしているUIRefreshControlのアニメーションが終了し、ScrollViewの上部に隙間が縮んでレイアウトが元の状態に戻ります。

正しい例

f:id:taku_oka:20170811185656g:plain

UIRefreshControlの使い方としては、

  • ユーザーが指を離してからロードを開始
  • ロードが終わってからendRefreshing()を呼ぶ

が正しい。

実装としてはUIScrollViewDelegateWKNavigationDelegateを使う必要がある。

ユーザーが指を離したタイミング

UIRefreshControlのイベントが発火したらユーザーがPullToRefreshを開始したとみなし、このプログラムではフラグ isRefreshingByPullをtureにしている。

WebViewのドラッグ終了タイミングはUIScrollViewDelegatefunc scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool)でとれるので、 そのときにisRefreshingByPullがtrueならPullToRefeshで指を離したタイミングとみなし、webview.reload()でロードを開始した。

ロードが完了したタイミング

WKNavigationDelegateで下記のどちらかを使うといい。

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!)

WebViewのコンテンツが完全にロードされたタイミングで呼ばれる。 通信状況やコンテンのサイズ次第ではかなり遅くなることもあるので、使わないことにした。

func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!)

Webページのリクエストとレスポンスの受け取りが完了し、コンテンツのロードが始まったタイミングで呼ばれる。

コンテンツのロードが始まるタイミングまでは画面が空白になってしまい状態がわからなくなってしまうので、ここまではUIRefreshControlをアニメーションさせておきたい。 なのでこれが呼ばれたタイミングでendRefreshing()を呼んだ。

コード

import UIKit
import WebKit

class GoodExampleViewController: UIViewController {

    let webview = WKWebView()
    let refreshControl = UIRefreshControl()

    var isRefreshingByPull = false

    override func viewDidLoad() {
        super.viewDidLoad()

        webview.frame = view.frame
        view.addSubview(webview)

        webview.scrollView.delegate = self
        webview.navigationDelegate = self

        webview.scrollView.refreshControl = refreshControl
        refreshControl.addTarget(self, action: #selector(self.didStartPullRefresh(sender:)), for: .valueChanged)

        webview.load(URLRequest(url: URL(string: "https://www.apple.com/")!))
   }

    func didStartPullRefresh(sender: UIRefreshControl) {
        isRefreshingByPull = true
        refreshControl.beginRefreshing()// UIRefreshControlのアニメーションを開始
    }

    func didEndDraggingToPullRefresh() {
        webview.reload()// ユーザーが指を離したらリロード
    }

    func didEndLoadToPullRefresh() {
        refreshControl.endRefreshing()// UIRefreshControlのアニメーションを終了
        isRefreshingByPull = false
    }
}

extension GoodExampleViewController: WKNavigationDelegate {

    func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
        if isRefreshingByPull {
            didEndLoadToPullRefresh()
        }
    }
}

extension GoodExampleViewController: UIScrollViewDelegate {

    func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        if isRefreshingByPull {
            didEndDraggingToPullRefresh()
        }
    }
}

JavaScriptで定期的にWi-FiをOFF➜ONする

僕は普段はSwiftを書いているのですがインスタントな雑スクリプトJavaScriptで書きます。

カフェでドヤマックしてたのですがWifiの調子が悪くて定期的にon/offしていたので、それを自動化したいと思い雑なnodeJSを書きなぐりました。

Wifiを「切」にして3秒後に「入」にする`を60秒ごとに行うスクリプトです。 Shell Commandをタイマー実行しているだけですが。

toggleWifi.js
const exec = require('child_process').exec;

var intervalSec = 60;

function main() {
    console.log("start to toggle wifi");
    setInterval(function() {
        toggleWifi();
    }, 1000 * intervalSec)
    toggleWifi();   
}

function toggleWifi() {
    console.log("toggle...");
    changeWifiPower(false);
    setTimeout(function() {
        changeWifiPower(true);
    }, 1000 * 3);
}

function changeWifiPower(isOn) {
    var nextState = isOn ? "on" : "off";
    var command = "networksetup -setairportpower en0 " + nextState;
    doShellCommand(command);
    console.log("wifi turn " + nextState);
}

function doShellCommand(command) {
    exec(command, (err, stdout, stderr) => {
      if (err) {
        console.log(err);
      }
      console.log(stdout);
    });
}

main();

使い方

$ node toggleWifi.js

WKWebViewのリダイレクト時の挙動

WKWebViewのリダイレクト周りの挙動が分からなかったので調べてみました。 (iOS10 / iOS9) (Swift3.2)

リダイレクトはWKNavigationDelegateで検知するしかなさそう

自分の実験の中ではWKWebViewではリダイレクトのステータスコード(300系)を観測できなかった。 どうやらWKWebViewが内部的にリダイレクトは処理してしまっているようだったので、 WKNavigationDelegateによってそのリダイレクトによる遷移を検知した。

主要なWKNavigationDelegateのメソッド

func webView(WKWebView, decidePolicyFor: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void)

Decides whether to allow or cancel a navigation.

  • リクエストを開始するときに呼ばれる。そのまま遷移をさせるか、キャンセルするかを決める。
  • リダイレクトが起これば連続してよばれる。
  • navigationTypeを見るとクリックよる遷移か、リダイレクトによる遷移かがわかる。

func webView(WKWebView, decidePolicyFor: WKNavigationResponse, decisionHandler: (WKNavigationResponsePolicy) -> Void)

Decides whether to allow or cancel a navigation after its response is known.

  • レスポンスヘッダが返ってきたときに呼ばれる。そのまま遷移をさせるか、キャンセルするかを決める。
  • このあとにリダイレクトでまたnavigationActionが呼ばれることもあるので、ここで終わりとは限らない。

func webView(WKWebView, didCommit: WKNavigation!)

Called when the web view begins to receive web content.

  • リダイレクトが終わってコンテンツのロードが始まった時に呼ばれる。

リダイレクトの流れ

リダイレクトが終わるまで

webView(WKWebView, decidePolicyFor: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void)

webView(WKWebView, decidePolicyFor: WKNavigationResponse, decisionHandler: (WKNavigationResponsePolicy) -> Void)

連続で呼ばれ続け、

リダイレクトが終わってコンテンツのロードが始まると

webView(WKWebView, didCommit: WKNavigation!)が呼ばれる。

気をつけること

  • decidePolicyFor: WKNavigationResponseが呼ばれても終わりとは限らない。さらにそのままdecidePolicyFor: WKNavigationActionが呼ばれることがあり、didCommitが呼ばれるまで終わりとは限らないので気をつける。
  • didCommitが呼ばれて初めてリダイレクトが終了したとわかるが、Webコンテンツのロードが始まらない限り最後のURLがわからない。

P.S.

その他のWKNavigationDelegateのメソッド

didStartProvisionalNavigationdidReceiveServerRedirectForProvisionalNavigationは呼ばれない事もあったのでよく分かっていない・・・

func webView(WKWebView, didStartProvisionalNavigation: WKNavigation!)

Called when web content begins to load in a web view.

これが呼ばれるとリダイレクトがおわりURLが決定しているようだった。これのあとに最後のリクエストかレスポンスがきた。

これのあとにリクエストが来た場合はそれが最後のリダイレクトなのか、そのあとにdidReceiveServerRedirectForProvisionalNavigationが呼ばれ、レスポンスヘッダがきた。

※ なぜか、呼ばれないこともあった…

func webView(WKWebView, didReceiveServerRedirectForProvisionalNavigation: WKNavigation!)

Called when a web view receives a server redirect.

リダイレクトされたときの最後のリクエストのあとで呼ばれ、そのあとにレスポンスがきた。

※ なぜか呼ばれないこともあった…

発表しました。「Swiftがだいたい読めるようになるセッション」

先日、社内勉強会でエンジニアがiOSを雰囲気で読めるようになるのを目指すLTをさせていただきました。

Swiftはとても読みやすい言語なので、普段読んだことのないエンジニアの人でも要所さえ抑えてしまえば言語自体は簡単に読めると思います。

加えてiOSアプリケーション開発の全体像を掴めば、iOSプロジェクトがだいたい雰囲気読めるようになるのではないかと思いこの発表をしてみました。

リーディングの題材としてはこちらのオープンソースプロジェクトをお借りしました。 少しリファクタしましたが、手頃なサンプルプロジェクトを1から作る時間が無かったので助かりました。 github.com

最近はAndroid界隈でKotlinというSwiftにとても似てる言語が普及している流れも有りAndroidエンジニアとiOSエンジニアでお互いにコードを読みやすくなっているので、僕もそろそろKotolinのAndroidを雰囲気で読めるようになっておきたいところです。

シンタックスハイライトしたコードをスライドに貼り付ける

Keynoteパワポのスライドにシンタックスハイライトしたコードを貼り付ける方法です。

f:id:taku_oka:20170719030629p:plain

インストー

まず、highlightをインストールします。

$ brew install highlight

実行

Keynoteなどに貼り付けるにはリッチテキスト方式で貼り付ける必要があります。 例えば codes.swiftを リッチテキスト方式でクリップボードにコピーするにはこんなコマンドになります。

$ highlight codes.swift -O rtf | pbcopy

貼り付け

f:id:taku_oka:20170719030936p:plain

オプション

さらにオプションを使えばシンタックスハイライトのスタイルやフォント、タブのサイズなどを詳しく設定することも可能です。

$ highlight codes.swift --style=github --replace-tabs=4 --font=NotoSansMonoCJKjp-Regular --font-size=36 -O rtf | pbcopy

※フォント名はPostScript名で、MacではFontBookから確認可能です。

他にも色々なオプションがあるみたいです。 詳しくは -h オプションでヘルプを呼び出してみてください。

$ highlight -h

kicksterter/ios-oss を観察してみて思ったこと(その1)

海外のクラウドファンディングサイト kicksterter.com は自社の iOSアプリソースコードGithubで公開しています。

kicksterter/ios-oss

f:id:taku_oka:20170710015303p:plain

なかなか評判が良さそうだったのでソースコードを観察してみました。

最も印象的だった部分はだいたい他の人の記事で分かりやすくまとめられていたので、 僕は俯瞰的な視点で思ったことをダイジェスト的に書くことにしました。

動機

僕は現在新規のiOSプリプロジェクトに参加しています。 アプリ開発は最初が肝心なので、なるべく他のプロジェクトを参考にして構成を参考にしようと思い、オープンソースで公開されているkicksterterのiOSプロジェクトを観察してみました。

アプリ開発は最初が肝心

ソフトウェア開発も最初が肝心です。 初期段階のフェーズでの判断次第で、未来の開発環境が大きく変わります。

アプリ開発でも、最初に作った基盤の上で開発していくので長期間の開発を経るほどにその基盤を変更するコストはどんどん膨らんでいき修正するのが難しくなっていきます。 開発をするほどに技術的負債は膨らんでいってしまいます。

初期段階で急いで開発をして技術的負債を多く残しつつもアプリをリリースし、汚いコードやイケてない仕組みの上で実装を続けていくしかないプロジェクトも数多く存在しているはずです。

途中で技術的負債を解消するために経営陣を説得してサービスの進化を一時停止させてコストを投資し途中からリファクタを頑張ることも可能ですが、

最も良い方法は、もともと最初から技術的負債を最小限に抑えることです。

開発初期の段階で適切な設計や仕組みを導入する決断をしていき、なるべく良い開発環境を整え、なるべく良いレールを敷いておくことが大切です。

そのためにまずはOSSなどでしっかりとした構成で作られたプロジェクトに目を通しておくと大変参考になります。

読んでみた結果

README.mdに、アプリのコードをOSSとして公開する理由は “教育のため” と書いてありましたが、まさに勉強になりました。しっかりとしたモダンな構成になっているので、iOSアプリのプロジェクト例としてとても参考になります。 このプロジェクトはだれでも見ることができるので、新規でアプリを作る人は目を通しておけば何かと得るものがあると思います。


前置きが長くなってしまいましたが、

印象的だった点をいくつかピックアップして書いていきます。

どこまでOSSにしてるのか

オープンソースとはいえ、さすがにサーバーサイド周りの機密情報は公開してません。 秘密にすべき情報は Secrets.swift に集約されており、オープンソース上では例としてウソの情報が入っていました。

Secrets.swift の isOSS を true に変えるとAPI層がモックに差し替わる仕組みになっています。


どのように storyboad / xib を使っているか

セオリー通り、 1つのViewController に対して 1つのstoryboadで、基本的に全てのUIViewController, UIVIew, Cellに対してstoryboard or xibが存在していました。

ちなみにモダンにStackViewを使用しています。

全てのStoryboardは enum で管理されていて、enumからインスタンス化していました。

どこまでをStoryboadで定義しているか

storyboard / xib では主にレイアウト情報を定義しているだけでした。 フォントや色などのスタイルの定義はコードで設定しています。

最もポピュラーなやり方だと思う

やはりkicksterterのように storyboad / xib を主にレイアウトのみで使うことで レイアウトのコードを省きつつ、Viewの構造を理解しやすくする。フォントや色はアプリ全体で統一した値を使うためにコードで書く。 というのが多分 現状では一番ポピュラーなやりかたなんじゃないかと僕は思っています。(意見ください)

(今後は、Xcode9で追加されるAssets catalogsのnamed colors supportなどによってアプリで統一している色やフォントもInterfaceBuilder上で定義しやすくなってこの辺もstoryboardで定義するのが良くなっていくのでしょうか。)


独自のライフサイクルを追加している

kicksterterではUIViewUIViewControllerに独自のライフサイクルメソッドである bindStyles()bindViewModel() を追加していました。

独自のライフサイクルといっても、もともとあるライフサイクルと同じタイミングで特定のメソッドを呼ぶようにしているだけです。 実装を見るとわかりますが、method swizzling か extensionを使ったオーバーライドによってそれを実現しています。

スタイルを定義する場所とViewModelとのバインドを定義する場所が決まっているとプロジェクト全体で統一感がでて綺麗になるし実装する時に迷わずにすむので良さそうです。

独自のライフサイクルメソッド

  • bindViewModels(): ViewModelをバインドする処理を書くところ
  • bindStyles(): Viewのフォントや色などを適応するところ

それらが呼ばれるタイミング

UIView

  • bindViewModels() -> awakeFromNib
  • bindSytles() -> traitCollectionDidChange

UIViewController

  • bindViewModel() -> viewDidLoad
  • bindStyles() -> traitCollectionDidChange + viewWillAppear(初回のみ)

基本的にどちらもほぼ初期化のタイミングで呼ばれます。

traitCollectionDidChangeでbindSytles()してるのが新鮮でした

このメソッドは本来 画面回転のイベントをフックするタイミングで呼ばれるので、ここでスタイルの適用をしているのが個人的には参考になりました。

traitCollectionDidChange呼ばれるタイミングは以下です。

UIView の traitCollectionDidChange が呼ばれるタイミング

  • 自身がaddSubviewされた時
  • 画面回転時

UIViewController の traitCollectionDidChange が呼ばれるタイミング

  • アプリケーションの起動時
  • ViewControllerが初めてロードされたタイミング
  • 画面回転時

traitCollectionDidChangeが呼ばれるタイミングは画面回転時だけではなく、初期化時にも呼ばれるので確かにスタイルを設定するタイミングとしては適していそうです。


また、bindViewModelsのタイミングとしてawakeFromNibを使う辺り、徹底して全てのViewでInterfaceBuilderを使用する方針が伺えます。


ViewModelの書き方がイケてる

KicksterterのViewModelでは、inputsoutputs を protocol として定義しており、 「入力」と「出力」が明確に別れて列挙されており大変シンプルでわかりやすいインターフェイスを実現していました。

これはとても分かりやすいと思ったので自分のプロジェクトでも導入を検討しています!

protocol MyViewModelInputs {
   // タップや文字入力などを受け取るメソッドたち
}

protocol MyViewModelOutputs {
   // Viewがobserveするプロパティたち
}

// 使用側はこの型を使用することでシンプルに扱える
protocol MyViewModelType {
  var inputs: MyViewModelInputs { get }
  var outputs: MyViewModelOutputs { get }
}

final class MyViewModel: MyViewModelType, MyViewModelInputs, MyViewModelOutputs {
  // 本体クラスはよしなにinputs, outputsのインターフェイスを実現
}

実際のコードはこんな感じです: CommentsViewModel.swift

また、Swiftはヘッダファイルがないので、こういったアプローチをすることでもヘッダファイルのようなインターフェイスを実現することができるという気付きも新鮮でした!

詳細は@muukii0803さんの「Kickstarter-iOSのViewModelの作り方がウマかった 」をご覧ください。


MVVMで画面遷移

画面遷移の方法が参考になりました。

  • ViewModel側に showHoge のような Observableを公開しておき、
  • ViewControllerがそれを監視して画面遷移します。
  • (次の画面生成に必要なオブジェクトはObsesrvableで流れてきます。)

SignupViewController#L144:

self.helpViewModel.outputs.showWebHelp
  .observeForControllerAction()
  .observeValues { [weak self] helpType in
    self?.goToHelpType(helpType)
}

詳細は@star__hoshiさんの「[MVVM] kickstarter/ios-oss での画面遷移のやり方」をご覧ください。


独自の演算子を使用している

|>, ||>, ?|>, , >>>, <<<, <>, .~, ^*, , .., >•>, %~, %~~, <>~

elixirにもあるパイプライン演算子 |> しか読めませんでしたが、確かにこれは便利かもしれません。 演算子を使わない場合と比べると、とてもシンプルに書けているのではないでしょうか。

let backing = .template
  |> Activity.lens.id .~ 62
  |> Activity.lens.project .~ (.cosmicSurgery
    |> Project.lens.photo.med .~ ""
    |> Project.lens.photo.full .~ ""
    |> Project.lens.stats.fundingProgress .~ 0.88
  )
  |> Activity.lens.user .~ (.template
    |> User.lens.name .~ "Judith Light"
    |> User.lens.avatar.small .~ ""
  )
  |> Activity.lens.category .~ .backing

ActivitiesViewControllerTests.swift

所見ではマジでイミフです。 けど、慣れれば記述力と可読性が上がるのだと思います。

public let activitySampleCellStyle = baseTableViewCellStyle()
  <> UITableViewCell.lens.backgroundColor .~ .clear
  <> UITableViewCell.lens.contentView.layoutMargins %~~ { _, view in
    view.traitCollection.isRegularRegular
      ? .init(top: Styles.grid(4), left: Styles.grid(30), bottom: Styles.grid(3), right: Styles.grid(30))
      : .init(top: Styles.grid(4), left: Styles.grid(2), bottom: Styles.grid(3), right: Styles.grid(2))
}

ActivitySampleStyles.swift

演算子の定義をしてるファイルはこちら

いいかも

プロジェクトの学習コストは少しは上がりますが、とてもスッキリ書けそうです。 学習コスト上がるとはいえ超短期でプロジェクトに入るエンジニアなんて滅多に居ないので独自の演算子の導入は検討してみても良いかもしれないなと思いました。


あとがき

随所で参考になるのでなかなか読み応えがありました。 まだ完全に読み込めてないのでもう少し読んでみて、もう1本記事を書きたいと思います。

他にも mozilla-mobile/firefox-ios 等も読んでいきたいと思いました。

間違いの指摘等ございましたら気軽にコメントよろしくお願いします🙏