なるようになるかも

力は多くの場合、その人の思いを超えない。

OSLog にいい加減に移行するべきなの?

と思って調べた内容のメモ。

結論から言って、watchOS みたいなパフォーマンスにシビアなデバイスで、マルチスレッドが絡む部分をロギングしたいとかじゃない限り、普通のロガーでよさそう…。

OSLog の優位点

メモリ上の循環バッファにバイナリでログを保持しておき、何らかの障害が発生した時点で初めて外部に出力するので、観測者効果が最小限で、余計な I/O が発生せずログが容量を逼迫することがない。小容量な SSD がメインストレージの、モバイル端末を考えて再設計されてる感がある。

Activity Tracking により、そのログがどういうコンテキストで出力されたのかを視覚的に表示することができる。

その他、機微情報の扱いなど、ロガーとして全体的に再設計されているらしい。

OSLog の欠点

ログが単純なテキストファイルではないので、これを横断的に検索する手段が存在しない。閲覧に際しても、基本的には Sierra 以降のコンソール.app を使う必要がある。

Activity Tracking が長らく Swift に対応していない。

効率よくログを蒐集するためには、独自に文字列を組み立てるのではなく、Obj-C ライクな書式文字列を覚える必要がある(その書式文字列の一覧がどこにあるのかは分からない…)。

OSLog の使い方でいまいち分かりにくかったところ

Xcode の Console に出る情報が少なすぎて使いにくい

コンソール.app を使うのがほぼ前提なところがある。

OSLog.default って使っていいの?

subsystem と category の情報をログに含める必要がないなら別に使っても問題ないらしい。

subsystem と category って何?

ログを識別するために設定できる任意の文字列。

  • subsystem は慣例的に Reverse domain name notation で実行しているアプリの識別子などを渡す。
  • category はその中でログメッセージを分類するのに使用する。

ビデオの中では、メッセージ本体とタイムスタンプに別の category を設定するという使い方が紹介されていた。

ログレベル良くわからない

ログレベル 意味
Default 循環バッファにログを出力し、メモリが溢れたとき圧縮して外部に保存する。追跡に使うデータは基本これ。
Info 循環バッファにログを出力する。エラーが発生しない限り、外部に書き出されず捨てられる。
Debug 設定を変えない限り、メモリには取り込まれない。開発用。
Error 常に外部に書き出される。プロセスレベルのエラーを報告するためのもの。
Fault 常に外部に書き出される。システムレベルおよびマルチプロセスのエラーを報告するためのもの。

とりあえず残しておいて損はない情報は Info にざくざく詰んでおくと、障害発生時にだけ書き出してくれる。

Error / Fault の違いは、Activity Tracking の振る舞いに起因しているっぽい?あんまり分かってない。

ラッパークラスやラッパー関数を作ってもいいの?

ラップするとどこでエラーが発生したのかという情報が失われるのでダメらしい。

Avoid wrapping os log APIs in other functions. If you wrap it in another function you then lose our ability to collect the file and line number for you.

参考にしたもの

Unified Logging and Activity Tracking

OSLog について知るならまずこのビデオを見ないと始まらない。

Logging: Using the os_log APIs

サンプルコード

Fix Bugs Faster using Activity Tracing

2014 年の WWDC の頃にあった Activity Tracing の話。

トレーシングは UIKit に統合されてて Target-Action で勝手に Activity を作るみたいな挙動だったはずなんだけど…。

Unified Logging and Activity Tracing

Unified Logging の話とか、Activity の Swift3 Wrapper の話とかがある。

iOS デザイン実装はつらい話のあとがきみたいなの。

Qiita に久々に記事を書いたのですけれど、なんか途中で力尽きていろいろツッコミどころが多い感じになってしまった。

qiita.com

わたしとしてはコメントがとても勉強になったので良かったのですけれど…。

以下、書ききれなかった内容の供養みたいなの。

画像リソース周りの話

  • ついにラスタライズされた偽ベクタ画像じゃなくて、本物のベクタ画像が使えるようになった
  • Slicing の機能はわりと便利だと思うのですけれど、あまり使われてるのを見ない…

みたいなことを書こうと思って忘れたらしい。

Named Color の話

iOS11 以降しか使えないので(R.swift とか SwiftGen とかでバックポートできると思いますが)、まだ本格普及しないと思うのですけれど、これの動作はわりと謎です。

単に Assets Catalog から UIColor を生成して KVC で設定してるだけかと思いきや、シミュレータで見ると View がロードされてから、実際に表示されるまでの間に色空間が別物になっていたりとか。iOS10 辺りで入ったカラーマネジメントとか Display P3 が関係しているのだと思いますが、あんま詳しくないので適当なことしか書けない…。

// viewDidLoad()
UIExtendedSRGBColorSpace 0.562 0.529 0.342 1
// viewDidAppear()
kCGColorSpaceModelRGB 0.562 0.529 0.342 1 

なので、Named Color で指定した場所は、viewDidLoad() で色を変えても反映されないですし、KVC で値を渡すだけの @IBInspectable で Named Color を指定しても (Missing) という表示になります。

Named Color の登場で割を食ったのが Color Literal で、実体が RGBA 値をコードにベタ書きしてるだけなので、Named Color としては使えません。(なのに、Media Library から Named Color をドラッグすると、Color Literal に化けてしまうのがよくない)

Attributed String の話

JLREQ の要求が高すぎて目眩がしつつ、せめてベタ組みと連続約物の処理くらいはちゃんとやりたいなと思いながら、どう実装すればいいのか悩んでいる今日この頃。

iOS でリッチテキストの描画をするときは、Attributed String を使うわけですが、System Font で日本語の1行だけの Attributed String を作ると、Paragraph Style に lineSpacing を指定したときに、高さ計算に失敗するというバグ?があります。

qiita.com

これはそもそも Attributed String に System Font を渡すのが、想定された使い方ではない…という感じなんでしょうか。実際、Storyboard 上では System Font の Attributed String は作れませんし。

普通のテキストには System Font を推奨しているわりに、Attributed な場合は System Font を使うとダメっぽいのが罠として優秀。

追記:もうちょっと調査した

UIFontDescriptorヒラギノを持つカスケードリストを作っても上記の問題が回避できないですね。

// これは擬似コードなんですけれど、こういう書き方をすると bese にない文字は .cascadeList で指定したものにフォールバックされる
let baseFontDescriptor = UIFontDescriptor(name: "HelveticaNeue-Medium", size: size)
let hiragino = UIFontDescriptor(name: "HiraginoSans-W3", size: size)
let font =  UIFont(descriptor: baseFontDescriptor.addingAttributes([.cascadeList: [hiragino]]), size: size)

System Font 固有の問題というわけではなく、iOS 内臓のヒラギノの抱える闇の一端っぽい感じ?UIFontDescriptor はめっちゃバギーな感じがするのでもっと使われてヤバさが広がってほしい…。

@IBDesignable の話

@IBDesignable って実際使われてるんでしょうか。分かってくると結構便利だと思うんですけれど、トラップがいろいろあってとっかかりが悪い感じ。

  • init(frame:) を実装する必要がある
    • UITextView の場合は、textContainer を引数に取るイニシャライザが必須
  • layer プロパティの操作や addSubLayer(_:)maskLayer など CALayer に対する各種操作はできる
    • class function の layerClass を変更しても反映されない
  • TARGET_INTERFACE_BUILDER で処理を分岐させると、その範囲はリファクタリングの対象外になったり、IB でのみビルドエラーになっていても気付かなかったりする
  • Storyboard 上で致命的なクラッシュが発生した(IUO の Outlet とかを触ったとか)は、~/library/Logs/DiagnosticReports/ にログが吐かれるので読めば分かる
  • Editor > Debug Selected Views を実行すると、当該のカスタム View のイニシャライザや draw(_:) が走って、breakpoint で止まる

アプリケーションを実行せずに View だけでデバッグできるのは良い機能だと思います。

JSONDecoder で型がごちゃ混ぜの JSON 配列をデコードする

ルートが配列で、最初の要素はその後の要素の個数を表す、という JSON があったときに、どういう Decodable を書けばいいのか?で少し悩んだので。

[
{"count" : 5 },
{"name" : "itemA", "value" : "valueA" },
{"name" : "itemB", "value" : "valueB" },
{"name" : "itemC", "value" : "valueC" },
{"name" : "itemD", "value" : "valueD" },
{"name" : "itemE", "value" : "valueE" }
]

それぞれの要素が、以下のいずれかの型に decode されるというところまでは共通。

struct Count: Codable {
    let count: Int
}

struct Item: Codable {
    let name: String
    let value: String
}

方法1. 取り得る値の両方でパースする

Stack Overflow の回答で見つけたのが、singleValueContainer() でそれぞれの型に対して decode() できるかチェックする方法。

enum Response: Decodable {
    case count(Count)
    case item(Item)
    
    init(from decoder: Decoder) throws {
        if let count = try? decoder.singleValueContainer().decode(Count.self) {
            self = .count(count)
            return
        }
        if let item = try? decoder.singleValueContainer().decode(Item.self) {
            self = .item(item)
            return
        }
        fatalError()
    }
}

singleValueContainer() のリファレンスを見ると、

Returns the data stored in this decoder as represented in a container appropriate for holding a single primitive value.

「単一のプリミティブな値を持つ」というような説明があって、SO の回答でも IntString を decode していたので、ES の仕様でいうところの Primitive Data Types しか decode できないのだと勝手に勘違いしていたけど、Decodable でも普通にいけた。

方法2. 配列を直接全部走査する

このケースだと、「先頭の要素は全件のカウントが入っている」ということが自明なので、unkeyedContainer() で配列のコンテナを取得して、要素一つ一つに対して decode() していくという方法の方が使い勝手が良かった。

struct Response: Decodable {
    let count: Int
    let items: [Item]
    
    init(from decoder: Decoder) throws {
        var unkeyedContainer = try decoder.unkeyedContainer()
        
        count = try unkeyedContainer.decode(Count.self).count
        
        var result: [Item] = []
        result.reserveCapacity(count)
        
        while !unkeyedContainer.isAtEnd {
            result.append(try unkeyedContainer.decode(Item.self))
        }
        items = result
    }
}

JSON の構造が固定ならこちらの方がよさそう。

逆の操作をすれば encode() もできるので、Codable にすることもできる。

extension Response: Encodable {
    func encode(to encoder: Encoder) throws {
        var container = encoder.unkeyedContainer()
        try container.encode(Count(count: count))
        try items.forEach { try container.encode($0) }
    }
}

Android Architecture Components 雑感2。

主に LifecycleLiveData について。

Lifecycle

某ポエムサイトにあったクラス図がしっくりこなかったので、自分で書き直してみました。

f:id:quesera2:20170529210511p:plain

Lifecycle のキモは、ライフサイクルメソッドのオーバーライドはもうやめましょう、ということ。

なぜかというと、ActivityFragment を容易に Fat にする上に、処理を追うのが直感的ではなくなってしまうから。

その代わり、ActivityFragmentState を持つように変わります。ライフサイクルイベントが発生すると、どこからともなく(後述) Event が投げられて、これを受けて State の更新が行われます。

StateEvent は公式サイトの以下の図の通りです。

f:id:quesera2:20170529211323p:plain

Handling Lifecycles より)

このイベントの変更を受け取りたいクラスは、LifecycleObserver インターフェースを実装します。これはマーカーインターフェースです。任意のオブジェクトが LifecycleObserver になれます。

その上で、@OnLifecycleEvent アノテーションを付与して、実際のそれぞれの State 変更時の処理を実装します。

@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
public void onResume() {
    // RESUMED State に遷移したときに行って欲しい処理
}

あとはこの LifecycleObserver を、ライフサイクルイベントを受け取りたい LifecycleOwnerActivity または FragmentService)の LifecycleRegistry に登録して、購読させます。

これにより、画面を構成する部品単位で、ActivityFragment から切り離された状態で、ライフサイクルに応じた処理が書けるようになります。

同じライフルサイクルメソッドに、複数の処理が書かれることがないので、コードは読みやすくなりますし、LifecycleObserver マーカーインターフェースを実装するだけでよいので、再利用性も高くなります。

  1. 任意のコンポーネントがライフサイクルを持てる
  2. Activity にベタに書かなくて良い
  3. 再利用可能

という特徴から、「不可視の Fragment じゃん」という感じですが、実装上でもそのテクニックが使われています。

LiveData

さて、LiveData はリアクティブな感じで値を通知する簡易なモジュールで、Rx のオペレータが使えないので正直メリットを感じないものなのですが、LifecycleObserver を実装している、という点は見逃せません。

例えばバックグラウンドで通信を行う場合、結果を表示する UI が既にない、という状況はありがちです。

LiveData は 「STARTED または RESUMED でない状態ならば、値を通知しない」という仕組みになっています。購読、購読解除については、instanceofLifecycleOwner であれば勝手にやってくれると思いますので(たぶん)、あんまり細かいことを気にしなくていいのがウリです。

類似品に、RxJava の親戚の RxLifecycle がありますが、こちらは RxFragment のようなライフサイクルを監視する特定のクラスを継承しなければいけないのに対して、Lifecycle は Andorid の仕様の穴を突いたような素敵実装になっており、ActivityFragment の実装を選ばない、という点がメリットとして挙げられると思います。

とはいえ、個人的には使わなさそうな感じです。

重要なのは、今までのライフサイクルメソッドを太らせていく記述方法から、データドリブンな記述(Rx に近い考え方)へ転換しましょうね、というのが LivecycleLiveData の狙いなので、もし Rx があんまり好きじゃないならスルーしていいと思います。

Lifecycle の実装

www.jianshu.com

内部を追いかけていたらこのサイトに行き当たって、知りたいことはほぼほぼ説明されていました。

Android Architecture Components のソースコードはまだ公開されていないので、現時点で内部構造を語っているのは世界で唯一このサイトだけだと思います、たぶん。

registerActivityLifecycleCallbacks()Activity のライフサイクルコールバックを受け取って、onActivityCreated() で不可視の ReportFragment を埋め込んでライフサイクルを監視してるわけですね。

この ReportFragment のライフサイクルメソッドの呼び出し時に、

private void dispatch(Event event) {
    if(this.getActivity() instanceof LifecycleRegistryOwner) {
        ((LifecycleRegistryOwner)this.getActivity()).getLifecycle().handleLifecycleEvent(event);
    }
}

という感じで、もし ActivityLifecycleRegistryOwner を実装していたら、getLifecycle().handleLifecycleEvent() を呼び出すという単純ながら力強い実装になっています。

registerActivityLifecycleCallbacks() を行うタイミングがないように思えますが、LifecycleRuntimeTrojanProvider というダミーの ContentProvider で実現されている、というのが面白ポイントです。ContentProvider がアプリケーションプロセス起動時に立ち上がるのを利用しているわけですね。

ViewModel と画面遷移

いまだにしっくりこないのが、ViewModel で画面遷移をどう実現するか?という部分です。

Handling LifecyclesViewModel のベストプラクティスの項目には、

Never reference a View or Activity context in your ViewModel. If the ViewModel outlives the activity (in case of configuration changes), your activity will be leaked and not properly garbage-collected.

【私訳】 ViewModel に View または Activity context への参照を持たせないでください。もし ViewModel が Activity より長生きした場合(configuration changes の発生時など)、Activity はリークして、適切に GC に回収されません。

とある通り、ViewModel に画面遷移に必要な Context を持たせたくないのですが、とはいえどうすればよいのか。

公式のサンプルだと、NavigationController を DI しているのですが、これは単一の Activity だからできるという感じだし、画面の数だけ navigateToXXX() メソッドを増やしていくのは微妙だなーという感想しかないわけです。

実際的には、Intent フラグを与えたり、Bundle でパラメータを受け渡ししたいというようなケースも多々あるので、もうちょっと汎用的に使えるものが欲しい。

また、このサンプル実装だと、commitAllowingStateLoss() で誤魔化していますが、FragmentManager もライフサイクルと切っても切れない Android のつらい問題のひとつで、ViewModel のスコープから画面遷移をキックすると、IllegalStateException に悩まされるのがオチだと思っていて、その辺のライフサイクルの問題までは、まだまだ解決しそうにないなーという感想なのでした。

Android Architecture Components 雑感。


Architecture Components - Introduction (Google I/O ‘17)

解説はこのビデオを見るのがとても分かりやすいです。

機械翻訳で日本語字幕が出せるので、英語が聞き取れなくてもだいじょうぶ。

いままでの Android の世界観

※人によってかなり違う気がします

f:id:quesera2:20170524223955p:plain

  • バックグラウンド処理は Service を使うことができました
    • Service をキックするのは Activity でもいいし、SyncAdapter とかを使う手もあったり
    • この辺使うのが面倒だったりで、AsyncTask の不治の病に陥ってたり、RxJava 使ったりの方が多いですよね…
  • ContentProvider を経由することでデータベース実装を抽象化
    • やってるの、正直あんまり見たことない…
    • 連絡帳プロバイダとかにちょっと凝ったクエリ(グルーピングとか)を投げようとすると、途端にテーブル構造を意識したハックみたいな記述になって全然抽象化されてない説がありますよね
    • Android 2.2 とかの時代に、android:exported (外部公開するかどうか)のデフォルト値が true だったせいで迂闊に使うと脆弱性があるみたいなイメージが付いたのが悪いんでしょうか…
  • データの取得・更新反映は CursorLoaderContentObserver を使えばおおよそ解決
    • これは割と普通に便利だと思うんですよ。Loader 単体でテストも書けるし

これからのざっくりとした感じ

f:id:quesera2:20170524230321p:plain

  • ライフサイクル問題の解決として、ViewModel が登場
    • 他にもなんかいろいろあるっぽいけどあんま追えてないです
    • Activity または Application のライフサイクルで生存するインスタンスみたいな感じ
    • ViewModel はあるものの、MVVM フレームワークにこれといった決定打がなく、また DialogFragment なんかをどうすればいいのか?という部分までは解決できていないという印象
  • データがローカルにあるのか、あるいは通信で取得するかはリポジトリで隠蔽
    • リポジトリLiveData を返す、LiveData は通信や DB アクセスを行った結果をリアクティブな感じで渡す
    • ちなみに現状で、Data Binding もしたい場合は、ObservableFieldLiveData の変換をどうにかしないとダメっぽい、辛い
    • ざっくり使ってみた感じだと、LiveData は正直使う必要性を感じない、普通に RxJava で良いと思う
  • 公式 ORM として、コードファーストな感じで DB を操作できる ROOM が(今更)登場

あくまでこれらは指針

念頭としてあるのは、開発者が行うべきことは、Activity のライフサイクルと格闘したり、FragmentManager の実装の深淵を追いかけたりすることでもなく、アプリケーション開発を通じてユーザーに価値を提供するということ。

その方法論として、「Andorid 標準のコンポーネントを使って、無理に Android のルールで拘りすぎる必要がない」ということが提示された、というのが一番大きいのかなぁと思いました。

Volley とか Loader の頃とかはまだ、Android のルールの上で利便性を高めることが意識されていたように思うのですが、ユーザーの間で発展していった設計思想なんかを、民主主義的に取り入れていくというメッセージ性を感じます。(Kotlin 公式言語化にも通じるところがあり)

Architecture Components 自体、強制されるものではなく、部分的に取り込んだり、より発展させることができる、非常に高い自由度があるサポートライブラリになっていて面白いです。

「Android を支える技術 <I>」を読んだ。

この本いいよね…。

今 <II> も読んでるんですけれど、「uid ってどこで作られてるの?」っていう5年くらい疑問だったところが解決されてとても嬉しい。gid は意味なさそうなのになんで設定されてるの?っていうところまでは書いてなかったけど。

この本はものすごく技術書なんだけど、それでいて単純に読み物としても非常に面白いのですよね。

ガラケー時代の組み込みのようなアプリを作ってきた世代と、AndroidiOS でアプリを作り始めた世代の架け橋みたいな感じの内容というか、まさに自分が Linux カーネルとかいまいち分からず、Andorid のソースコードを読み解こうとしても低レイヤの世界で何が起きているのか完全に分かっていなかったところがあって、この本の内容はクリティカルヒットな感じでした。

ただ、アプリ開発しているような世代の人も、いまやスマートフォンコモディティ化して、アプリ開発の大規模化や長期化が進んだことで、「どうやってアプリを差別化してバリューを生み出すか」とか「どういう設計にすればアプリを保守していけるのか」みたいな、もっと上位レイヤな方向に興味が向かっていると思うのですよ。

この本がそういう方向で役に立つということはきっとなくて、でも、だからこそ、今このタイミングで、この熱量の Android 本が出てくるというところが感無量ですよね。

以下、サブタイトルが気になったので思うところを。

Project Butter

Android が「60 fps を実現するモダンな GUI エンジン」を目指していたのかというと、別にそうでもなかった気がします。

2.X 時代は GC で普通に固まってたから、Stop The World を回避するためにオブジェクトを作り置きするのが基本だったし、それで処理落ちを頑張って減らしたら実は液晶が 56fps しか出ないことを知って悲しみにくれたりしてました。

「ぬるぬるな iOS に対して Android はもっさり」が半ば諦めとして定着していたなかで、いよいよ Google が本気を出して改善に取り組みました。それが、Project Butter と呼ばれるものです。

しかしながら、この本では全然出てこない気がします。というのも、Project Butter が大々的に謳われていた JB の扱い自体がヒドいのです。曰く、「通常の使用に耐えなくなる」とか「停滞感と閉塞感」とか「開発者も普通っぽい」とか。

しかし、垂直同期が取れる Choreographer クラスが登場したのは JB です。それまでの Android は、なんと垂直同期なしでダブルバッファリングをしていたのです!バックバッファとフロントバッファの切り替えは何のタイミングでやってたの…?

これとは別に、トリプルバッファリングの仕組みも出てきました!(P233 を見るとこっちが現行の動きみたいですね)

垂直同期を取ってダブルバッファリングで描画する方法は、描画命令が 16ms 以内で収まる場合には理想的な 60fps になりますが、少しでも描画が間に合わなくなると、前フレームの描画でバックバッファがロックされている関係で、一気に 30fps に落ち込んでしまうという欠点もあるのです。

半分のフレームになるので、処理落ちした途端にガクガクになったように見えます。

これを抑制するのがトリプルバッファリングです。描画可能なバッファを常に裏に 2 枚持つことで、処理落ちでジャンクフレームが生まれても、フレーム落ちを滑らかにして分かりにくくする、という技術です。当然、画面 1 枚分の余計なフレームを描画するために、無駄な GPU とメモリを消費します。

JB の評判がすこぶる悪かったのは、そういう富豪的な改善方法に問題があったからなのでしょう。

Project Svelte

これに対して、機能を維持しつついかに軽量化するかというのが Project Svelte です。

この本にも KitKat で劇的に立て直したという話が出てきますが、その過程がとても面白いのです。メモリが 512 MB の改造 Nexus 4 を作って、開発チームはそれを個人の端末として日常的に使ったとかどうとか(KitKat開発者が語る、Android軽量化の裏話)。

これは現在進行形で、Android O でも引き続いてバックグラウンド処理の取り締まりなどで強化されていっています。

しかし、裏でどんだけ電力食おうが許される野放図な感じとか、暗黙の Intentでゆるくアプリ同士が繋がるのは、Android の魅力でもあったと思うので、どんどん世知辛くなっていくのは、ちょっとだけ寂しい感じもしますね。

なお、普段使いは iPhone なので、Pixel が日本で発売されるまでは、この辺実際どんな感じなのか知りません!

追記

<II> も読み終わりました。

アプリケーションの世界で閉じている iOS に対して、ActivityServiceContentProvider というコンポーネント単位で横断して連携できるアプリを、Intent という共通したメッセージングで行えるところが Android のよさだと思っていて、ちょっと Activity と画面遷移の話に寄りすぎかなぁという感想もあったりしましたが、おもしろかったです。

もうちょっと Linux カーネル力があれば理解度が上がったのになぁ…。

ところで、<I> の6章のグラフィクスアーキテクチャの話については、AOSP の Porting - Graphics を読むとより面白い気がします。おすすめ。

「よくわかる AutoLayout」読んだ。

よくわかるAuto Layout iOSレスポンシブデザインをマスター - リックテレコム書籍情報

前提

わたしはコード、VFL、Storyboard で制約を書きます。ライブラリは特に使いません。

なんで全部覚えたの?って疑問に思うかもしれません。わたしも最初はいずれかひとつだけ身に付ければいいと思っていましたが、いまはそれぞれが相互補完する関係にあると思っています。

  • コードによる制約は、初見では複雑ですが、慣れればそうでもないです。付与できる制約に制限がないのがメリットです。
  • View の位置関係を示す場合、VFL は非常に簡潔に記述できます。覚えておけばデバッグ時にも便利です。ただこれ一本だとできないこともあります。
  • Storyboard/InterfaceBuilder による制約の付与は、プレビュー機能など利点が多いです。ただし込み入ったレイアウトを実現するいくつかのハックを知っている必要があります。
  • 暗黙の制約(translatesAutoresizingMaskIntoConstraintstrue のままにする)も割と便利です。ドキュメントには autoresizing mask で指定した振る舞いを duplicate するとあるのですが、これの意味は未だに分からんです。

適材適所でうまく組み合わせるのが重要だと認識しています。

UPDATE: 暗黙の制約の仕様は Xcode7 と Xcode8 で別物になるらしい。いままで autoresizing mask ガン無視だったのが、ちゃんと反映されるようになるとか。まじかよ。

本の概要

iOS 開発を初期からやっている人はきっと必ず本棚にあるであろう、レジェンダリィな UIKit 本の著者が技術監修をしている、AutoLayout 攻略本です。

公式リファレンスもあります

developer.apple.com

そもそも AutoLayout 攻略本が必要なのか?公式ガイドで十分でないか?という疑問があると思います。

Apple の公式には日本語訳されたガイドがあるのです。しかし、「固有の寸法」「不定なレイアウト」「収縮性/膨張性の優先度」など、個人的にはピンと来ない感じの訳なので、初学者にはお勧めできません。

少し前に読んだ「SwiftではじめるUI設計&プログラミング」は、この辺の言い回しを、日本語として分かりやすく表現する苦労が感じられて良かったです(UI 設計の本というより、初級者向けレイアウトの作り方の本なのでそういうタイトルの方がよかったのでは?というのと、Swift1.X、iOS8 の本でなければ…という感じ)。

読書感想文

いわゆる、個人の感想ってやつです。

よかった点

硬派な内容

原色強めの表紙やタイトルから、なんとなく初級者向けなイメージを受けましたが、複雑なレイアウトを実現するためのパターン、実践的なハックも網羅されています。

ハマりがちな Self-Sizing Cells や UIScrollView への制約付与の仕方、さらに無限スクロールしたい UIScrollView への制約の付与の仕方も丁寧な解説があってとても参考になりました。

基本的なビューのデバッグ技法から、AutoLayout 固有の制約のデバッグ方法まで一通り書いてあるので、とりあえずこの本が片手にあればなんとかなりそうな気がします。

AutoLayout のレンダリングの裏側で起きていることを詳述している

ここはまだ自分も理解が追いついていない部分なのでまた読み返しますが…。

AutoLayout において viewWillLayoutSubviews:viewDidLayoutSubviews: は何なのか、とかそういう部分まで掘り下げて書いてある本は和書ではこれしかないと思います。

iOS9 の最新の知識を得られる

まだ iOS8 を切れなくてつらぽよですが、

  • UIStackView
  • UILayoutAnchor

について丁寧な解説があります。iOS10 ではこの辺に大きな仕様変更はなかったので、とりあえずしばらく使える知識なはずです。

よくなかった点

よくわからなくなくない?

基礎的な部分から始まりはしますが、ある程度 AutoLayout で苦しんだユーザーを想定しているような感じです。そういう人が開眼するには間違いなく良書だと言い切れます。

一方で、これから Swift を初めて、ちょっとした iOS アプリを作ってみよう…という人には敷居が高すぎる本だとも思います。

3章から UIWindowmakeKeyWindow()、ルートビューコントローラーみたいなトピックが入ってきますが、iOS7 から始めた人はたぶんこの辺のおまじないの話で辛くなってしまうような…。

トレイトコレクションを理解する上で、UIWindowUIScreenUIViewController がどういう関係にあるのか、という知識は必須なので、AutoLayout 自体がむずいってのが根本的な原因な気がしますけれど。

Width/Height Attribute に対する誤解

21ページにあるこの記述。

・Width、Height
Width、Height はそれぞれ、インターフェースオブジェクトの幅と高さを示します。この Attribute を用いる場合、この制約は1つのオブジェクトで完結するため、制約の SecondItem は nil になります。

これは根幹的な間違いだと思うのですけれど、わたしの誤読?

EqualWidth や EqualHeight の制約をコードで書くには SecondItem に同じ幅・高さにしたい View を指定します。

コードで作る制約は VFL と違って Multiplier を指定できるので、親 View とのパーセンテージでサイズ指定することをできます。個人的には、Adaptive なレイアウトを作るうえでは欠かせない知識だと思っています。

サイズクラスの話が本格的に出てくるのが遅め

レスポンシブデザインを語る上で、AutoLayout と SizeClass は不可分だと思うのですけれど、「サイズクラスとトレイトコレクション」は一番最後の章になっています。

名前そのものは、2章でも「Adaptive なレイアウトを構築する上で重要だ」として挙げられてはいます。

ですので、この本は AutoLayout にフォーカス絞り、そこから SizeClass まで話を膨らませないという組み立てにしたのかもしれません。