ReSwift とは、iOS アプリを Redux のように作れるようにしてくれるライブラリです。
スマホアプリを作っていると
という要求が出てくると思います。そんなときに、ReSwift でステートを一元管理し、UI の更新を RxSwift の I/F に合わせて使えると結構便利です。
ということで、メモ書きなので、さっとソースを貼り付けておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
|
これで、ReSwift の State
を次のように書くことができます。
1 2 3 4 |
|
ReSwift を RxSwift のように使うだけであれば、上の拡張でいいのですが、たくさんステートが出来てくると、 関係ないステートが更新されたときにも変更が通知されてしまって、パフォーマンスを気にすることが出てくるかもしれません。
そんなときは、distinctUntilChanged
を使って、監視しているステートが更新されたかどうかを確認するようにするといいと思います。
State
自体を ==
で比較できるように、State に Identifier
を導入して、更新されたかどうかをチェック出来るようにしてみます。
(というのも、State は構造体(struct
) で作ることになると思うので、同一のステートかどうかの一致が大変なのです。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
|
複雑そうなことをやっていますが、要はインスタンスを生成するたびに一意の数値を割り当てて、 状態が更新されたらその値をインクリメントするという方法で、数値比較だけで状態が変わったかを判断できるようにしています。
この識別子コンポーネントを State に持たせるようにして
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
こんな感じで、ステートが更新したときに、identifiableComponent.update()
を呼び出すと状態が更新されたことをマークします。
これで、ステートが更新されたときだけ UI を更新するようなコードを次のように書けるようになります。
1 2 3 4 5 |
|
パフォーマンスも気にせず使えるようになりました。
既存のプロジェクトからの抜粋なので、一部省略している箇所があります。
]]>細かいニュアンスは間違っているかも。。
基本用語の整理です。
大雑把に言うと、Observer Pattern (オブザーバパターン) と Stream/Sequence Programming を合わせたものが Reactive Extensions です。
オブザーバパターンにおいて、観測される対象を Subject と呼びますが、Rx では観測される対象のことを Observable と呼びます。観測する側は Observer (オブザーバ) なので、観測される側は Observable なわけです。
Rx では、Observable なものを subscribe
(購読) することで、Observable から送られてくるデータを受け取ります。この、Observable からデータが流れてくることを Stream (ストリーム) と呼びます。流れてくるデータは Element と呼ばれます。
1 2 3 4 5 6 7 8 |
|
ストリームには状態が3種類あり、それぞれ Next
、Error
、Completed
になります。
ストリームから次のデータが流れてきたことを表します。正常処理と考えて構いません。
Observable な対象が何かしらエラーを起こし、観測したかったデータが流れてこなかったことを表します。Rx では、Error を起こしたストリームは閉じられます。つまり、次のデータが流れてこないことを表します。
Observable な対象のストリームが閉じられ、この Observable な対象からは今後データが流れてこないことを表します。
観測を取りやめたい時には、dispose
を呼び出します。いつ dispose するかを使い分けられるように、Dispose
の実装がいろいろあります。
Observable は観測対象のことでしたが、Rx においては観測可能な事柄のことを Subject (事象) と呼ぶほうが一般的だったりします。Subject を観測するわけですが、観測するデータのことは Behavior (振る舞い) と呼ばれます。Subject
/ Behavior
は Reactive Extensions における一般的な説明となりますが、こと Rx Programming においては、Observable
と Subject
、Element
と Behavior
は似て非なるものとして扱われています。
Observable な対象は、2つの種類にわけられます。Hot
と Cold
です。
Hot な Observable とは、観測対象である Observable が作られたときから、任意のタイミングでデータを送信することができるものになります。
実はこの Hot Observable のことを、Rx Programming では Subject と呼ばれています。Subject は任意のタイミングでデータをストリームに流すことができ、同時に Observable なわけです。
Subject は任意のタイミングで Behavior (振る舞い) を決められることから、Subject / Behavior はセットで使われる言葉になります。
1 2 3 4 5 6 |
|
Cold な Obserable は、任意のタイミングでデータを流すことができません。いつデータが流れてくるのかわかりませんし、観測対象がいなければ、そもそも振る舞いが起きることもありません。
よく、API 通信を Observable にしますが、subscribe (観測) を忘れると、API 呼び出しがされないことになりますので注意が必要です。
1 2 3 4 |
|
Rx は UI プログラミングで語られることが多いと思いますが、プログラムのあらゆる境界を Observable でつなぐことで、テスタビリティが向上します。
Observable でつなぐことにより、直接のデータの生成元に依存せずにプログラムを記述することができるようになるので、テスト時だけデータ生成部分をいじることで、例えば API 通信結果を画面に表示するようなプログラムでも、API 通信を行うことなくデータを生成し、Stream に流すことで色々な依存関係を排除することが出来るのです。
1 2 3 4 5 6 7 8 9 10 11 |
|
また、Rx には Stream の観測を任意のスレッドで行うスケジューリングの機能が備わっているものがほとんどですので、時間の掛かるデータ生成処理はバックグラウンドで行い、UI の表示処理のところだけメインスレッドで行うといったことを容易に行なえます。
1 2 3 4 5 6 7 8 |
|
プログラムを書いていると、データソースの値を加工して別のところで使うという場面が多々あります。
このとき、何も考えずにプログラムすると、データソースが変更されても結果は変わらないという挙動になります。
1 2 3 4 5 6 7 8 9 10 11 |
|
こういった挙動でかまわないという場合もあるでしょうが、ほとんどの場合データソースの変化に合わせて挙動が変わってくれたほうが嬉しかったりします。そんな時は全部 Observable にします。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Rx には、Stream を操作するためのいろいろな演算が用意されています。Stream を別の形に変換する map
や、複数の Stream を結合する merge
や concat
がなどがあります。
Stream の合成は使いドコロとしては API 通信などの時間の掛かる処理とキャッシュデータを返すストリームとを合成するなどが考えられます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
[参考] iBeacon(3) - リージョン監視とレンジング - Enamel Systems
そこで、1秒間隔だと通知間隔が短すぎるので、5秒間隔とか1分間隔とかに変えたい時にどうするかですが、CLLocationManager
クラスに設定があればよかったのですが、特になさそうなので、RxSwift
を使ってストリームのフィルタで対応する方法のメモです。
CLLocationManager
の初期設定とかは参考サイトを見てください。
1 2 3 4 5 6 |
|
Rx
の sample
を使って、ストリームを interval
毎にサンプリングしてやるだけです。
Time-shifted sequences - Introduction to Rx
RxSwift
も使いやすくて、めっちゃ便利です!
いつも Signal
を自分で制御するイディオムを忘れてしまうので、メモ書きです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
ankurp/Dollar.swift という JavaScript の Lo-Dash や Underscore と同じ感じでコレクションを扱うためのライブラリがありますが、 each
が配列しか受け付けることが出来ないので、SequenceType
を受け取れるように拡張する方法のメモです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
使い方はこんな感じ。
1 2 3 4 |
|
配列の中身が AnyObject
だった時に、型指定するときとしない時でどっちも動くようにするのに、メソッドを2つ定義しました。
このへん、Generics の達人のかたに、もっといい書き方あるよって教えて欲しいです。
ついでに reduce
も。
1 2 3 4 5 6 7 8 9 |
|
使い方はこんな感じ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Ruby の map っぽい感じで使えるにしたかったので。
]]>定義や概念などは、語るコンテキストで捉え方が違ったり、論争の元になるので、今回はあえてこういう定義付けをさけ、実践で利用する場合にどういうメリットがあるのか、どういうふうに使えばいいのかに焦点を当てました。
一部、寝ぼけていたのかおかしな部分があったりもしますが(React.js が Flux アーキテクチャに則っているとか変なこと書いてます。。。)、今号はいろいろおもしろい話が満載ですので、是非読んでみてください。
]]>こんにちは。iOS8 から SeparatorInsets をゼロにする方法が変わりました。今までの UITableView
の separatorInset
に UIEdgeInsetsZero
を入れるだけだと、iOS7 では効きますが、iOS8 では効かなくなりました。
1
|
|
iOS8 からは UIView
に追加された layoutMargins
というプロパティにも UIEdgeInsetsZero
をセットする必要があります。ただこのプロパティ、iOS8 以降でしか利用できないプロパティなので、分岐が必要になります。。
1 2 |
|
全体に適用するには UIAppearance を使うと便利です。iOS7 と iOS8 に対応したコードは次のように書けます。
1 2 3 4 5 6 7 8 |
|
Auto Layout
を使っていても問題は表面化しづらかったのですが、
iPhone6 で横幅が伸びてしまったことで、適当に Auto Layout
を使ってコーディングしていると残念なことになっているアプリが結構あります。
僕も iPhone アプリを開発しているのでこのあたりは結構気を使ってはいるんですが、いかんせん自分のやり方がほんとに正しいのか、やや疑問なところもあります。 そこで、今の自分のやり方を晒して、世の iPhone アプリ開発者の人に突っ込んでもらえればと思い記事を書くことにしました。
間違っている箇所もあると思いますので、ドンドンツッコミをお願いします!
このまとめ記事で、何が出来るようになりたいか。それは次の2点です。
UITableViewCell
(可変長高さ)を Auto Layout
を使ってキレイにつくりたいUIScrollView
(可変コンテンツ)を Auto Layout
を使ってキレイにつくりたいこんな感じのやつです。
UILabel
の中身によって UITableViewCell
の高さが変わるやつと、UIScrollView
の中身をページングで切り替えるときに中身の個数を動的に差し替えるやつです。
それぞれ作り方をさらします。
ソースコードは
にあるので、参考にしたい方はどうぞ。
ちなみに、iOS8 以降だけに対応するのであれば、TableViewCell.xib
で ContentView
に対して何も考えずに Auto Layout でレイアウトして、TableViewController
を継承したクラスで次のように tableView.rowHeight
に UITableViewAutomaticDimension
を設定し、tableView:estimatedHeightForRowAtIndexPath:
で UITableViewAutomaticDimension
を返すようにするだけで解決します。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
tableView
に estimatedRowHeight
というプロパティがありますが、これに UITableViewAutomaticDimension
を設定して、tableView:estimatedHeightForRowAtIndexPath:
を実装しない方法だと上手くいきません。バグなんじゃないか??。。。
作り方を晒す前に、まずは Auto Layout のおさらいをしておきます。
Auto Layout は Storyboard やプログラムから「制約」を利用してレイアウトを組み立てるものです。基本的に Auto Layout に対応したアプリでは、
UIView(frame: CGRectMake(xx, xx, xx, xx))
みたいな作成方法はしなくなります。
Auto Layout を使うと、”画面のこのコンポーネントから◯pxずらしてこのコンポーネントを配置する” や “このコンポーネントとこのコンポーネントのサイズを同じにする” みたいなことをなんとなく直感的に記述(または、設定)できます。
Auto Layout を Strobyboard 上で設定すると、その制約が実際にコンポーネントに反映されるのは、UIViewController#viewDidLayoutSubviews
のタイミングになるようです。
UITableViewCell で Auto Layout を使うときに注意しなければならないのは、awakeFromNib
のタイミングでは、セルの Auto Layout が設定されていないということです。
まぁ、これは当然といえば当然で、カスタムの UITableViewCell は再利用を前提に作られるので、実際にセルのコンテンツが設定されるは UITableViewDataSource#cellForRowAtIndexPath
の段階になるからです。
このメソッドの中でセルのコンテンツを設定するわけです。
で、まぁ普通に UITableView を使っているとドハマリするのが、セルの高さを返す UITableViewDelegate#heightForRowAtIndexPath
のメソッドが呼び出されるのが、cellForRowAtIndexPath
の前ということです。
セルの高さを計算できるのはセルのコンテンツが設定された後なんじゃないの?って思うわけですが、高さの問い合わせが来るのはセルのコンテンツを設定する前なのです。。
じゃあ、どうするかというと、僕はもう割りきって、heightForRowAtIndexPath
の中で cellForRowAtIndexPath
を呼び出して、セルにコンテンツを設定した後にセルの高さを計算するようにしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
|
高さを計算するときに、cellForRowAtIndexPath
を呼び出してセルにコンテンツを設定した後、セルの width にテーブルビューの width を設定してあげて、一度 cell のレイアウトを更新します。
セルの width にテーブルビューの width を設定しているのがミソ です。
Storyboard 上ではカスタムセルの横幅がデフォルトで320pxで作成されるので、iPhone5S まではセルの横幅を気にしなくてもセルの layoutIfNeeded
だけを呼び出せば勝手に中のコンテンツの高さ決まっているように錯覚していたのですが、
実は Storyboard 上で320pxでセルが作られているのでたまたま偶然いままではうまくいっていただけっぽいです。
本来は、セルの横幅が決まった上でセルのコンテンツの Auto Layout にしたがって layoutIfNeeded
しないと行けないのです。セルの横幅を設定せずに layoutIfNeeded
だけでコンテンツの高さに合わせてセルの高さを決めていると、
iPhone6 Plus になったときにセルの高さがおかしくなってしまいます。
セルの高さを計算するメソッドは heightForRowAtIndexPath
ですが estimatedHeightForRowAtIndexPath
というメソッドも用意されています。
このメソッドは何なのかというと、UITableView は大量のデータを表示する場合でも、表示領域+αの部分だけしかセルをインスタンス化しないという設計になっています。
ですので、cellForRowAtIndexPath
は表示領域+αの部分分だけ呼び出されるのですが、セルの高さの計算メソッドは、そうではありません。
スクロールバーの範囲を計算したり色々するために、セルの高さの計算は、データがどれだけ大量にあっても全ての数分呼び出されてしまうのです。
で、heightForRowAtIndexPath
と estimatedHeightForRowAtIndexPath
の違いですが、estimatedHeightForRowAtIndexPath
はセルの大まかなサイズを返すメソッドとして用意されています。
heightForRowAtIndexPath
と estimatedHeightForRowAtIndexPath
が両方用意されている場合は、全てのセルの高さを一旦 estimatedHeightForRowAtIndexPath
で計算したあと、
表示領域+α分の回数だけ heightForRowAtIndexPath
が呼び出されるという形になります。
estimatedHeightForRowAtIndexPath
を定義しない場合は、全てのデータ分だけ heightForRowAtIndexPath
が呼び出されるので、データが多い場合は結構きつくなります。
なので、忘れずに estimatedHeightForRowAtIndexPath
も定義しておきます。
返す値はなんとなくの定数(44pxとか)でもいいですし、UITableViewAutomaticDimension
を返すでもどちらでもよさそうです。
setNeedsLayout
はそのコンポーネントに、再レイアウトが必要なフラグを設定するメソッドになります。再レイアウトが必要なコンポネントは次のタイミングの layoutSubviews
の呼び出しの時に再レイアウトされます。
layoutIfNeeded
はそのコンポーネントが再レイアウトが必要なフラグが立っているかを確認して、再レイアウトが必要であれば layoutSubviews
を呼び出してくれるメソッドです。
コンポーネントのレイアウトを強制的に更新したい場合は
1 2 |
|
のコンボでレイアウトを更新します。この処理を行うと、Auto Layout にしたがってレイアウトが設定されます。
レイアウトを更新したらセルの高さが決まっているはずなので bounds.height
を返します。
UIView 関連のコンポーネントには、推奨サイズというのが設定されているコンポーネントがあります。例えば UILabel であれば、ラベルの文字数をきっちり表示できるサイズが推奨サイズになりますし、 UIButton であればボタン名が表示できるサイズが推奨サイズになります。
コンポーネントの推奨サイズを取得するメソッドが UIView#intrinsicContentSize
になります。実はこのメソッド、Auto Layout と深い関係があります。
例えば、UILabel の制約を次のように設定したとします。
親のコンポーネントに対して、Top、Trailing、Leading を 0 に設定しています。こうすることで UILabel が親のコンポーネントの上左右の padding が 0 で表示されるわけです。
ここでのポイントは、bottom に制約を設定していないことです。bottom に制約を設定していないので、本来であればこのコンポーネントは高さが決まらずに ambiguity(曖昧)な状態なはずです。
でも UILabel や UIButton は ambiguity にはなりません。これは、コンポーネントに推奨サイズが設定されているので、そのサイズでコンポーネントが表示されるからです。
UILabel を使って、テキストがどれだけ多くなっても全て表示したいというときには、このように bottom に制約を設定せずに、Lines
を 0 に、Line Breaks
を Character Wrap
に設定します。
コンポーネントの中には、推奨サイズを持たないコンポーネントが存在します。UIView
がそうです。UIView は子のビューのサイズによって自身のサイズが変わるため、
推奨サイズを持ちません。
逆に考えると、中のコンテンツのサイズが動的に変わる場合でも、UIView が親にいる限り、子のコンポーネントは好きにサイズが変わってよく、子のサイズが決まった段階で UIView のサイズ(bounds)が決まるということです。
この性質を使うと、UITableViewCell の中身が複雑に Auto Layout される場合でも、セルの高さを簡単に計算できるようになります。
カスタムの UITableViewCell には、ContentView
という UIView が予め用意されていますが、この ContentView 、いまいち上手く Auto Layout してくれません。。
やり方が悪いだけのような気もしますが、僕は ContentView の子に UIView を入れるようにして、この UIView の上左右の padding を 0 に設定してレイアウトを始めるようにしています。
bottom の制約を設定しないのがミソです。bottom の制約を設定しないことで、UIView は縦方向に自由に伸び縮みして、子のコンポーネントのサイズに合わせて高さが勝手に変わるようになります。
最後のコンポーネントを配置し終えたら、そのコンポーネントの bottom を親に対して設定をすれば、UIView の高さが子のコンポーネントの Auto Layout から決まるようになります。
上の画像では、最後の要素の UILabel の bottom を親の Preferred View に対して設定していますが、UILabel の推奨サイズの高さと Preferred View からの bottom の位置とでコンフリクトが起きます。
Storyboard 上では、コンフリクトが起きると、赤い矢印マークがでます。実際には UILabel の高さを bottom の位置にまでの高さに設定することで矛盾は起きないのですが、 コレだと、高さが結局 UIView の高さに依存してしまい、UIView の高さは bottom を設定していないことから曖昧なサイズになってしまいます。
これだと思った様なレイアウトになりません。で、こんな時に設定するのが制約の priority です。制約同士でコンフリクトが起きた場合に、どちらの制約を優先的に適用するかを決めるのが priority になります。
今回は、ラベルの高さを推奨サイズで配置して、その位置を元に UIView の高さを決めたい ので、UILabel の bottom の制約の priority を下げます。UILabel の推奨サイズが決まる priority (Content Hugging Priority) が 251 なので、bottom の priority を 250 以下に設定すれば、 UILabel の推奨サイズを優先的に設定して、その高さからの bottom の位置が決まるようになります。
高さが決まらない UIView を使うと、Storyboard 上で制約に合わせてコンポーネントの位置を自動調整するときに困ることがあります。
そんな時は、高さ(height)を適当に設定して、制約のところの Placeholder Remove at build time にチェックを入れておきます。 こうすることで、Storyboard 上の作業のときだけ制約が設定されて、実行時には制約が削除される状況を作ることができます。
このように、セルの ContentView の子要素に Preferred View を用意して、コンテンツのサイズで UIView の高さが決まるようにすることで、 Auto Layout を使った配置の中でセルの高さを可変にすることが結構簡単にできるようになります。
他にもっと良いやり方があるかと思いますが、参考になればと思います。
UIScrollView も UITableViewCell と考え方は同じで、中のコンテンツを Auto Layout するときには Preferred View を用意して、 こいつにサイズを計算させて UIScrollView の contentSize を決めてやるようにします。
UIScrollView はビュー自体の frame
とコンテンツ部分の contentSize
の二つの要素を持ちます。frame はスクロールビュー自身の位置やサイズを表します。
contentSize はスクロールビューが表示するコンテンツのサイズを表していて、スクロールビューのサイズよりも大きい contentSize の場合には、スクロールビューの中でスクロールが発生するということになります。
UIScrollView で気を付けないといけないのは、contentSize は中に含まれるコンポーネントの frame や bounds で決まるわけではない ということです。
UIScrollView の contentSize はコンポーネントの intrinsicContentSize
によって決まります。ここが UIScrollView でハマる一番のポイントかなと思います。
つまり、intrinsicContentSize (推奨サイズ)が決まるコンポーネントのみをコンテンツとして表示する場合には contentSize は Auto Layout が自動で計算してくれるということです。
例えば UIImageView を UIScrollView の中に入れたとします。UIImageView の intrinsicContentSize は UIImageView の frame や bounds ではなく、表示する UIImage の size になります。 なので、UIImageView だけを UIScrollView に入れると、UIImageView のサイズをいくら Storyboard 上で設定しても、勝手にスクロールされてしまうのです。
UIScrollView の contentSize は intrinsicContentSize によって決まるというのは、上で書きました。
じゃあ、contentSize をプログラムで上書きしちゃえば上手くスクロールするんでは?ということで、さがすと出てくるのが大体 viewDidLayoutSubviews
の中で
contentSize を設定すればいいじゃんっていうプログラム例です。
1 2 3 4 5 6 7 |
|
ぶっちゃけ、僕もこの方法が結局妥当かなという結論に達しました。先に UIScrollView に画像を動的に追加して、ページングできるようにするコードを掲載します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
|
UIScrollView の中に画像を3つ表示して、それをページングスクロールするコードになります。Paging Enabled のオプションは Storyboard 上で設定しています。
画像を直接 UIScrollView にいれてしまうと、intrinsicContentSize の問題で表示が意図したとおりにならないので、UIImageView をラップする PreferredView を作成しています。
PreferredView の中に UIImageView を隙間なく配置し、PreferredView を ScrollView の縦横のサイズと同じサイズにして、Auto Layout で左から順番に配置しています。
ぶっちゃけ、この処理であれば、UICollectionView
を使うほうが早いし楽だし美しいと思います。
UIScrollView で Auto Layout を使って頑張ってスクロールコンテンツを配置したいときに、参考になるといいと思っています。
]]>RACCommand で検索処理とかしてるときに、ローディング画面やインジケータを表示したいということがあると思います。そういうときに使える Signal のイディオムです。コードは Swift で書いています。
MVVM で viewModel が searchCommand を実装しているとします。また、ローディングインジケータの表示には MBProgressHUD を使っているとします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
executing
Signal はこういう風につかうんですね。
で、いつか直るだろうと思ってたけど、GM でも直っていない Swift の多次元配列の定義の謎に関してです。だれか理由分かる人いたら教えてください。。
下のようなコードがあるとします。
1 2 3 4 5 6 7 8 9 |
|
typealias
で別名つけて2次元配列を定義しているだけです。要素が Dictionary
になっています。ここまでは普通にコンパイルが通ります。
ですが、これをこんなふうに…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
配列の要素を増やしていきます。そうするとだいたい 10件くらい からコンパイルが遅くなり、15件過ぎたくらいから コンパイルエラーが出るようになります。
1
|
|
意味がわからんです。。
仕方ないので
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
こうやって、プログラムでデータ登録するようにしました。。
]]>本書は、NetBeans API を構築した筆者がデザインパターンやコーディング作法だけではうまくいかない問題、後方互換性を維持したままライブラリを発展させる方法、を重点的に解説しています。なるほど、 デザインパターンや設計方法を学んだ次に読むべき本 として挙げられている理由がよくわかる内容になっています。
本書で扱う API という言葉は、例えばオープンソースライブラリのような、多くの人に共有されるものという位置づけになっています。社内で使うようなライブラリなどの API とはニュアンスが違う感じがしました。
一度リリースしたら、後方互換性を維持して発展させていかなければならない、そういった類のライブラリや API を作る開発者向けの本です。
本書のテーマは、次の2つに集約されると思います。
NetBeans API で培った経験則を余す所なく記載してくれているため、ボリュームがスゴイことになっています。また、作って終わりの API の作り方を教えているわけではないので、おそらくほとんどの開発者には なんとなくわかった気になるだけか難しくて後回しにする ような内容かもしれません。
はっきり言って、本書が役に立った!と感じる開発者は少ないかもしれませんが、少なくとも GitHub でソースコードを公開している開発者は頑張って読んでもらうのがいいと思いました。それくらい内容が濃く、経験者しかわからないことが書いてあります。
開発者は、API の現在のバージョンをコーディングしている場合に、未来について考えることが求められます。私に言えることは、それは、今までの API 設計でよく行われた方法ではないということです。また、今日までに書かれた書籍やその中の助言は、この種の思考にはあまり役に立ちません。それらには、単一バージョンの場合のデザインパターンが説明されていることがほとんどです。
これが、新しいデザイン本が必要な理由 です。今日のシステムはコンポーネントの組み合わせで出来ています。おそらく今後もこの傾向は変わらないと思います。コンポーネント利用者の経験(投資)をムダにしない為に、よりよい発展を目指す上で、後方互換性を維持することは重要なことになります。これが、本書を読む理由 です。
では、どのように API 設計をすればいいか。その方法も本書に書かれています。
API に優れた名前をつけることや 驚き最小の法則、シンプルで一貫性のある I/F にするというのは基本的にな事になります。こういったことを踏まえた上で、この API を利用してくれているユーザを尊重すること(次のバージョンでメソッド名を変更するなんてとんでもない!)が大切だと書かれています。
後方互換性を保ち、API のユーザの投資をムダにしない設計の方法を学びたい人、新しい視点を身につけたい人に本書はおすすめです。
- オブジェクト指向アプリケーションフレームワークには、伝統的なデザインパターンとは異なるスキルが必要
- クラスを API として扱って、頭痛の種を軽減
- 将来、改善できるように API の発展計画を準備
catchTo
の使い方を覚えたのでメモ。
リアクティブプログラミングの詳細は省くとして、RACSignal
のイベントには next
と completed
と error
があります。それぞれ、subscribeNext
、subscribeCompleted
、subscribeError
でハンドリングできるやつです。
で、HTTP API 等を呼び出す際に API 呼び出しの結果を JSON にパースして、結果をモデルに設定するみたいなことをやりたい時に、次のように行います。(Swift で記述しています。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
こうすると、API の呼び出しが成功した場合には subscriber.sendNext
の結果がきちんと self.model
に設定されるわけですが、API の呼び出しが失敗して subscriber.sendError
が呼び出されてしまうと、例外がなげられます。
ではどうするか。
そこで catchTo
を使います。
1
|
|
catchTo
は sendError
が呼び出された場合に代わりに投げる RACSignal
を指定します。そうすることで、ネットワークエラー等で API の呼び出しが失敗した場合には、RACSignal.empty()
で何も起きなかったことになる(正確には sendCompleted が呼び出される)ようになります。
sendError 時に処理を行いたい場合には catch
を代わりに使用します。
1 2 3 4 |
|
以上です。
]]>Dictionary
を扱うときのメモです。API 呼び出しのレスポンスを JSON で扱いたい時に Dictionary<String, AnyObject>
として扱う際のポイントです。
1 2 3 4 5 6 7 |
|
最後のはなぜコンパイルエラーになるかというと、Dictionary
の subscript
が2種類定義されていて、期待したのと違う方が呼び出されているからです。
1 2 3 4 5 6 |
|
json["foobar"]
の戻り値は (Key, Value)
か Value?
のどちらかですが、as String
を付けた際に Optional ではないと判断されてしまい (Key, Value)
が戻り値の型と判定されます。それでコンパイルエラーになるわけですね。
期待した通りに取得するには、json["foobar"]
の戻り値を Value?
として扱う必要があるので、
1 2 |
|
のどちらかでアクセスする必要があるわけです。!
を付けると unwrap されるので nil
が入っていると実行時エラーになります。逆に as AnyObject? as? String
でアクセスすると Optional 型になってしまいます。
API のインターフェースと相談して、どちらの型で処理するか決めるといいんじゃないかと思います。
]]>Swift では配列を mutable/immutable の区別なく定義することができるようになりました。
1 2 3 |
|
let
で定義すると immutable、var
で定義すると mutable になり、配列の代入はすべてコピーで行われるようになっています。
で、ハマったのが次のようなコードです。
1 2 |
|
Force Unwrapping 型とでも言うんでしょうかね?変数に必ず値が入ってることを保証するために !
が付いた型です。
この型で定義した配列には、値の追加や変更ができなくなっています。。
これで何が困るかというと、UIKit
使って UIViewController
のサブクラスにプロパティを定義する際に
初期化を viewDidLoad
で行う時には、!
をつけないと initializer
が必要になってしまうので、プロパティの定義には !
を付けていました。
そうすると、mutable で扱いたかったプロパティなのに、変更できないという問題にぶち当たったわけです。。どうすんだこれ。。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
対応としては、一旦変数で受けて、変更後に元に戻すっていうので何とかなりますが、コンパイラの方でなんとかならんもんですかね。。
1 2 3 4 5 |
|