hiragram no blog

iOSとか

"ReactiveCocoa vs RxSwift"の和訳

https://www.raywenderlich.com/126522/reactivecocoa-vs-rxswift

-

Functional Reactive ProgrammingはSwift開発者の間でますます人気が高まってきている方法論である。それは複雑な非同期処理のコードを書きやすく、理解しやすくすることができる。

この記事では、FRPの中で人気のあるライブラリ、RxSwiftとReactiveCocoaを比較する。

まずはFRPとは何かを簡単におさらいして、そのあと2つのフレームワークの細かい比較を見てもらう。読み終わる頃には、どちらのフレームワークが自分にあっているか判断ができるようになっているだろう。

リアクティブに行こう!

Functional Reactive Programmingとは何か?

Note: もしもう既にFRPのコンセプトをわかっているなら、次の"ReactiveCocoa vs RxSwift"セクションに進んで欲しい。

FRPは、Swiftがアナウンスされる前から既にオブジェクト指向プログラミングに対抗する方法論として人気を獲得していた。Haskellに始まりGo,JavaScriptなど、FRPの思想に影響を受けた実装が見つけられるだろう。なぜか?FRPの何が特別なのか?そしておそらく最も大事なのは、このパラダイムをSwiftでどう活用できるのか?

FRPはConal Elliottが提唱したプログラミングパラダイムである。彼の定義はとても具体的な意味論でここで見ることができる。雑に言うと、FRPは以下の2つのコンセプトの組み合わせである。

  • Reactive Programming
    • listenして、それに応じてアクションできるような非同期なデータストリームに着目。もっと学びたければこの素晴らしい説明を見て欲しい。
  • Functional Programming
    • 数学的な関数を用いた処理、不変性や表現性、そして変数や状態を減らすことに重きをおく。もっと学びたければSwift functional programming tutorialを見て欲しい。

Note: Andre StalzがオリジナルのFRPと実践的なアプローチについて彼の記事で説明している。"Why I cannot say FRP but i just did"

簡単な例

例を見るのが一番わかりやすい。ユーザの位置情報をトラックして彼女がコーヒーショップの近くに立ち寄った時にアラートしてくれるようなアプリを考えよう。

これをFRPの流儀に則ってプログラミングしようとすると

  • それに応じてアクションできるような位置情報のストリームイベントを発するオブジェクトを作る。
  • 発せられた位置情報イベントを見てコーヒーショップに近い位置情報があるかを探し、マッチするものをアラートする

ReactiveCocoaで書くと、こんな感じだ。

locationProducer // 1
  .filter(ifLocationNearCoffeeShops) // 2
  .startWithNext {[weak self] location in // 3
    self?.alertUser(location)
}

セクションごとに見ていこう。

  1. locationProducer は位置情報の変更を一定の周期で通知する。これをReactiveCocoaでは"signal"と呼び、RxSwiftでは"sequence"と呼ぶ。
  2. 次に位置情報の更新に答えるために関数型プログラミングの手法を使う。filter メソッドはarrayのそれと同じように動き、それぞれの値をifLocationNearCoffeeShops メソッドに渡す。もしtrueが返されれば、そのイベントは次のステップの処理に進むことができる。
  3. 最後に、startWithNextはイベントが届いた時に実行されるクロージャを設定する。

上のコードは配列の値を変換するようなときに使われるものとよく似ている。しかし、これは少しだけ賢い…。このコードは非同期に動くのだ。filter関数とクロージャは"位置情報イベントの発生に応じて"実行される。

このシンタックスは少し妙だと感じるかもしれないが、このコードの根底にある意図はとてもわかりやすいだろう。これが関数型プログラミングの美しさであり、 values over time(宣言的で、どのようにではなく何が起きたかを示す)というコンセプトによくフィットするところである。

Note: もしもReactiveCocoaのシンタックスについて学びたいなら、私がGitHubに公開した例を見て欲しい。

イベントの変換

位置情報の例では、コーヒーショップの近くかどうかを見るためにフィルタリングする以外は何もせずにただストリームの受信を開始しただけであった。

もう一つのFRPの基本的な部分は、イベントを意味のある情報に組み合わせたり変換したりすることである。そのために、高階関数を用いる。

ご想像の通り、Swift functional programming tutorialの中にあった、map,filter,reduce,combine,そしてzipなどだ。

それでは先程のサンプルを、繰り返されたイベントをスキップし、近づいている位置情報をユーザフレンドリーなメッセージに変換するように変更してみよう。

locationProducer
  .skipRepeats() // 1
  .filter(ifLocationNearCoffeeShops) 
  .map(toHumanReadableLocation) // 2
  .startWithNext {[weak self] readableLocation in
    self?.alertUser(readableLocation)
}

追加された2行を見てみると

  1. locationProducerから発されたsignalにskipRepeatsオペレータを適用した。これは配列のようなものを持たないオペレーションで、ReactiveCocoa特有のものである。この関数は繰り返しになっている値を弾くだけのフィルターである。
  2. filter関数が動作した後、イベントデータを他の型に変換するmapが使われる。ここでは CLLocationからStringに変換している。

次は、これらFRPの旨味を知るためにはじめるべき

  • シンプルなのに、パワフル
  • 宣言的アプローチによってコードはよりわかりやすくなる
  • 複雑なフローも管理や表示が簡単になる

ReactiveCocoa vs RxSwift

さて、FRPとはどういうもので、それがどのように複雑な非同期処理を簡潔にしてくれるか少しは分かったところで、2つの最も人気のあるFRPフレームワーク、ReactiveCocoaとRxSwiftについて、そしてどちらを選ぶのかを考えてみよう。

詳細に行く前に、それぞれのフレームワークの歴史を簡単に振り返ってみよう。

ReactiveCocoa

ReactiveCocoaはGitHubで生まれた。GitHubMac版クライアントを作る中で、開発者はアプリケーションのデータフローに苦労していた。彼らはMicrosoftのCE向けライブラリReactiveExtensionsに影響され、Objective-C向けの実装を作り始めた。

チームがObjective-C版のv3.0のリリースに向けて進んでいた時に、Swiftがアナウンスされた。彼らはSwiftの関数的な部分はReactiveCocoaにとってとても補完的だと感じ、後にv3.0としてリリースされるSwift版の実装にすぐとりかかった。v3.0のシンタックスはとても関数的で、カリー化とパイプライン演算子を利用していた。

Swift2.0でprotocol-oriented programmingが提唱され、ReactiveCocoaに重要なAPI変更をもたらした。v4.0ではパイプ来年残滓が廃止され代わりにプロトコル拡張が使われる。

ReactiveCocoaは執筆時点でGitHubのスターが13000以上集まっている人気のライブラリである。

RxSwift

MicrosoftのReactiveExtensionsに影響されてJavaScriptJavaScala、その他多くの言語にFRPの思想をもったフレームワークが生まれた。これらはFRPの実装に一般的なAPIを策定したReactiveXによって主導されており、これにより様々なフレームワークの作者がともに作業できるようになった。結果的に、RxScalaを使う開発者はRxJavaにも親しみやすくなった。

RxSwiftは最近ReactiveXファミリーに追加されたため、ReactiveCocoaのStar数には及ばない(執筆時点で4000)。しかしながら、RxSwiftはReactiveXの一部であり、今後の人気と長命さは約束されていると言えよう。

ここで面白いのは、RxSwiftもReactiveCocoaもともにReactiveExtensionsを祖先に持つということである!

RxSwift vs ReactiveCocoa

続いて詳細を見てみよう。RxSwiftとReactiveCocoaはFRPの実現の仕方に多少の違いがある。そのうちの幾つかを見てみよう。

Hot vs Cold Signals

通信のためのリクエストを作り、レスポンスをパースし、ユーザに見せるようなシーンを想像してみよう。

let requestFlow = networkRequest.flatMap(parseResponse)
 
requestFlow.startWithNext {[weak self] result in
  self?.showResult(result)
}

リクエストはsignalを購読した時に初期化される(startWithNextしたとき)。これらのsignalは、購読するまで"凍結された"状態なので、 cold と呼ばれる。

もう一つは hot なsignal。hot signalを購読した時、それは既に動作を開始しているかもしれないので、受け取ったイベントは3番目、4番目のイベントかもしれない。ぴったりな例を挙げると、キーボードのタップがそうである。キーボードのタップが「開始」するというのは妙だ。

おさらい:

  • cold signal は購読して初めて動き始める。購読者ごとに動作が開始される。requestFlow を3回購読するということは、3つのリクエストを発行することを意味する。
  • hot signal はもう既にイベントを送り始めてるかもしれない。新しく購読者が増えても、新しく開始されることはない。ふつうUIイベントはhot signalである。

ReactiveCocoaはhotとcoldそれぞれにSignal<T, E>SignalProducer<T, E>という型を提供している。一方RxSwiftはObservable<T>という型一つで両方を表現する。

hotとcoldの型が分かれているのといないのとで何が違うのか?

個人的には、signalの意味を知っているということは大事だと思っている。なぜなら特定のコンテキストでどのように動作するのかを知っておいたほうが良いからである。より複雑なシステムを作るときに、これは大きな違いとなる。

それぞれの型があるかどうかは関係なく、hotとcoldについて知っておくことは非常に大事である。

仮にhot signalを扱っているつもりでそれが実はcold signalだったとき、新たな購読者ごとに副作用が発生する。これはアプリケーションに甚大な影響を与えるだろう。よくある例だと、3,4個のエンティティがネットワークのリクエストをobserveしたがっているとき、それぞれの購読ごとに異なるリクエストが実行されるべきである。

ReactiveCocoaに1ポイント!

エラーハンドリング

エラーハンドリングの話をする前に、RxSwiftとReactiveCocoaにディスパッチされるイベントについて簡単に振り返ってみよう。両方とも、主に以下の3つのイベントを発行する。

  • Next<T>
    • これはT型の新しい値がストリームにプッシュされるたびに発行される。位置情報の例だと、TCLLocationである。
  • Completed
    • イベントストリームの処理が完了したことを示す。これ以降にNext<T>Error<E>が発生することはない。
  • Error<E>
    • エラーを示す。ネットワークリクエストの例だと、サーバーがエラーを返した時などに発生しうる。EErrorTypeに適合する型。これ以降にNext<T>Completedが発生することはない。

お気づきかも知れないが、RxSwiftのObservable<T>は持っていないがReactiveCocoaのSignal<T, E>SignalProducer<T, E>は2つの型パラメータを持っている。Eという型はErrorTypeのエラーを示す。RxSwiftでは内部でErrorTypeのエラーを生成している。

これは一体どういうことか?

実践面では、RxSwiftではエラーを発するやり方はいくつかあることを意味する。

create { observer in
  observer.onError(NSError.init(domain: "NetworkServer", code: 1, userInfo: nil))
}

上のコードはsignal(RxSwiftではobservable)を作成して、すぐにエラーを発生させている。

あるいはこうも書ける。

create { observer in
  observer.onError(MyDomainSpecificError.NetworkServer)
}

Observableは「エラーはErrorTypeであればいい」ので、送りたいものを送ることができる。しかしそれは以下の様なケースですこし厄介になる。

enum MyDomanSpecificError: ErrorType {
  case NetworkServer
  case Parser
  case Persistence
}
 
func handleError(error: MyDomanSpecificError) {
  // Show alert with the error
}
 
observable.subscribeError {[weak self] error in
  self?.handleError(error)
}

これは動かない。なぜならhandleErrorが引数に取るのはMyDomainSpecificErrorであり、ErrorTypeではない。あなたは次のことをしなければならない。

  1. errorMyDomainSpecificErrorにキャストする
  2. errorをキャストできなかったときのケースをハンドルする

1つめはas?を使えば簡単に対応できるが、2つめは少し面倒だ。見込みのある解決策はUnknownというcaseを追加することである。

enum MyDomanSpecificError: ErrorType {
  case NetworkServer
  case Parser
  case Persistence
  case Unknown
}
 
observable.subscribeError {[weak self] error in
  self?.handleError(error as? MyDomanSpecificError ?? .Unknown)
}

ReactiveCocoaにおいては、Signal<T, E>SignalProducer<T, E>を作った時、違う型のエラーを投げようとした時にコンパイラは文句をいえる。

ReactiveCocoaにもう1ポイント!

UIバインディング

UIKitのようなiOSの標準APIは、FRPの言葉をしゃべれない。もしもRxSwiftもReactiveCocoaも使わないならば、自分でAPIをブリッジしなければならない。例えば、tapのtarget actionをsignalやobservableに変換しなければならない。

ご想像の通り、多くの手間がかかる。だからReactiveCocoaとRxSwiftの両方はそれぞれ幾つかのブリッジとバインディングを提供している。

ReactiveCocoaにはObjective-Cの時代からの資産がある。Swiftにブリッジされたすでにある多くの資産も見つけることができる。これらの中にはUIバインドが入っているが、その他のいくつかの演算子はまだSwiftに書き換えられていない。もしObjective-Cで出来たクラスを使っているなら、Swiftの型に変換しなければならない(toSignalProducerのようなメソッドも用意されている)。

それだけでなく、ドキュメントを読むよりもソースを読むほうが時間がかかると感じる。これは知っておくべきことだが、理論や考え方のドキュメントは夫妻となっている。使い方の部分のドキュメントはしっかりしてるけどね。

これを補うために、数十のチュートリアルが用意されている。

一方、RxSwiftのバインディングは使っていて楽しい!大きなカタログが有るだけでなく、山ほどの見本完全なドキュメントがある。これだけでReactiveCocoaでなくRxSwiftを選択する理由になる人もいるだろう。

RxSwiftに1ポイント!

コミュニティ

ReactiveCocoaはRxSwiftよりもその歴史が長い。多数の開発者がいて、十分な量のチュートリアルがオンライン上にあり、SOのReactiveCocoaタグも非常に助けになるだろう。

ReactiveCocoaはSlackのチームを持っているが、209人しかいない小さなチームであり、多くの質問は回答されないままになってしまう。緊急の場合は、コアメンバーに直接メッセージを送らざるを得なかったし、おそらく他の人々もそうしているだろう。とはいえ、ほとんどの問題はオンライン上のチュートリアルで解決できるはずだ。

RxSwiftは新しくて、ほぼ一人の手で作り上げられている。Slackのチームもあり、961人のメンバーがいて、議論も活発である。疑問に答えてくれる人をいつでも見つけることができる。

全体的に、今のところいずれのコミュニティも異なる面で素晴らしく、ここでは引き分けとする。

どっちを選ぶべきなの?

Ash FurrowReactiveCocoa vs RxSwiftで言うところによると

聞いてくれ、もしあなたが初心者なら問題ない。もちろん、技術的な違いはあるが、いずれも初心者にはとっつきづらい。まずは片方試してみて、そのあともう片方を試してみて欲しい。そして自分自身に問うてみると良い!そうすればなぜそれを選ぶのか理解できるだろう。

私も同じアドバイスをするだろう。

しかしながら、もしあなたがどちらかを選ばなければならなくて、かつ両方を試す時間が無い時のために、ここに私の提案を書いておく。

ReactiveCocoaを選ぶべきシーン

  • システムをよりわかりやすく記述したい時。hotとcoldなsignalを型で区別すること、エラーの型をパラメータ化することは、システムをより良くするだろう。
  • 実績があり、多くの人に使われ、多くのプロジェクトで使われたフレームワークを使いたい時。

RxSwiftを選ぶべきシーン

  • UIバインディングがプロジェクトにとって重要なとき
  • FRP初心者で手厚い支援が必要なとき
  • RxJSもしくはRxJavaの経験があるとき。RxSwiftはReactiveXのorganization下にあるため、一つを知れば、他を知るのも容易である。

次はどこに行くべきか?

RxSwiftかReactiveCocoaか、どちらを選ぼうとももう後悔はしないだろう。いずれもシステムをよりわかりやすく記述するには素晴らしいフレームワークだ。

これもまた重要な事だが、一度RxSwiftやReactiveCocoaを知ると、互いを行き来するのはとても時間がかかるだろう。私の経験によると、ReactiveCocoaからRxSwiftに勉強のために移行した際は、エラーハンドリングが最もはまった場所だった。まあ、最も大きなマインドシフトはFRPそのものに慣れることで特定の実装は関係ないんだが。

以下のリンクは君のFRP、RxSwift、ReactiveCocoaとの旅の助けになるだろう。

私はあなた達がこれらの素晴らしいライブラリの一つを将来のプロジェクトで使ってくれることを願っている。

Enums as configurationは別にアンチパターンでは無いのでは

ちょっと前の記事だけど。

Enums as configuration: the anti-pattern via @jesse_squires

この記事が言いたいのは

  • enumインスタンスの設定をするのは、提供された選択肢から選ぶしか無いので利用者側の自由度が低く良くない。
    • ライブラリやフレームワークがそうなっていると、選択肢を増やしたかったらPR投げてマージしてもらわないといけない。
  • Swiftのswitch文はenumを網羅しないとコンパイルエラーになるから、あちこちにエラーがでて悲しいからよくない。

ということらしい。

で、以下が僕の主張。

僕はパターンを網羅していないとコンパイル通してくれないSwiftのswitchは好きだ。同じ理由でenum使ってswitchするときにdefaultを使うのは嫌いだ。

Swift3の新しいアクセス修飾子について考察

3行で

  • Swift3から新たなアクセス修飾子 open,fileprivateが追加された
  • privateの扱いが変わった
  • ゆるい順に、open,public,internal,fileprivate,private

open

「モジュール外からアクセスでき、 サブクラス化(継承)可能である」ことを示すアクセス修飾子。以前まではpublicがこれを示していたが、publicは「モジュール外からアクセスできるが、 サブクラス化(継承)は出来ない」に変更された。

openはSwift2以前のpublicにあたり、Swift3のpublicはSwift2以前のpublicfinal修飾を付け足したもの?

fileprivate

「同じファイル内からのみアクセス可能である」ことを示すアクセス修飾子。Swift2以前のprivateと同じ。Swift3のprivateは、「定義されたスコープ内からのみアクセス可能」に変更された。

利用

openpublic、及びfileprivateprivateの使い方、使い分けについて考察する。

まず、Xcode8でできるSwift3のシンタックスへのコンバートをかけると、

  • publicはそのまま
  • privatefileprivate

になった。

近年Appleプロトコル指向プログラミング(Protocol Oriented Programming, POP)を推進しており

  • 参照型のクラスよりも値型の構造体を使いましょう(構造体は継承が無い)
  • 継承を使わずにプロトコルとエクステンションで実現しましょう

と考えているようで、コンバート時にpublicopenに置換されないのは「なるべく継承させないように作ろうね」というメッセージなのではと感じた。

privatefileprivateの使い分けについてはまだ考察中で、基本的にはprivateに寄せて参照できるスコープを狭くするべきだと思うが、一方でSwiftには

struct Human {
  var name: String
}

extension Human: HogeProtocol {
  func hogeMethod() {
    print("hoge")
  }
}

extension Human: FugaProtocol {
  func fugaMethod() {
    print(name)
  }
}

可読性の向上のためにプロトコルの実装をエクステンションとして切り出して書く派も存在し、もしプロパティnameprivateだった場合には2つのエクステンションからは参照できなくなる。

安全のためにスコープを狭くするべきか、可読性のために多少スコープの広がりを許容するかの問題だが、これはどちらを取るべきなんだろうか。

まとめ

基本的に自作の型でopenをたくさん使うとしたらライブラリ作者くらいであろう。Appleが継承地獄から脱却しようとしているのは明らかなので、それに沿う形で順応していこうと思う。

fileprivateについては、世間がどう受け止めるかという感じがするので、もうすこし様子を見たい。

参考

複数のプロトコルに適合する型、という書き方

最近知りました。

//: Playground - noun: a place where people can play

protocol Animal {
  func run()
}

protocol Vehicle {
  func drive()
}

struct Nekobasu: Animal, Vehicle {
  func run() { }
  func drive() { }
}

let bus: protocol<Animal, Vehicle>

bus = Nekobasu()

bus.drive() // ok
bus.run()   // ok

xibからビューのインスタンスを生成するコードの治安を良くする

AwesomeView.xibからAwesomeViewインスタンスを作りたい時、普通にUINibのイニシャライザを使って呼び出すと

let awesome = UINib(nibName: "AwesomeView", bundle: nil).instantiateWithOwner(nil, options: nil).first as! AwesomeView

こんなに治安が悪い。

  • nibNameを文字リテラルで指定している
  • force cast (as!)

この辺が治安を悪くしている。これをプロジェクト内のあちこちに撒き散らすのは本当にかなしくてみじめなことになるので、こんな方法を考えた。

まず「xibからインスタンス生成できる」という性質を示すプロトコルを作る。

protocol NibInstantiatable {
  // 上で言う、"AwesomeView"が入ってるプロパティ
  static var nibName: String { get }

  // そとから呼び出すメソッド
  static func instantiateFromNibWithOwner(ownerOrNil: AnyObject?, options: [NSObject: AnyObject]?) -> Self
}

次に、UIView用にデフォルト実装を持たせる。

extension NibInstantiatable where Self: UIView {
  static var nibName: String {
    get {
      // 上で言う"AwesomeView"みたいな、「自分のクラス名の文字列」を返す
      return String(Self.self)
    }
  }

  static func instantiateFromNibWithOwner(ownerOrNil: AnyObject?, options: [NSObject: AnyObject]?) -> Self {
    // 治安の悪いコードはここだけに閉じ込められる。
    return UINib(nibName: nibName, bundle: nil).instantiateWithOwner(ownerOrNil, options: options).first as! Self
  }
}

AwesomeView.swiftの変更は

- class AwesomeView: UIView {
+ final class AwesomeView: UIView, NibInstantiatable {

}

と、NibInstantiatableの指定をするだけ。protocolのメソッドでSelfを使っている都合からfinalの指定が必要になるが、ビュークラスの継承はしない、というルールが自分の中にあるので問題なし。

UIViewのサブクラスなのでこれでprotocol extensionに記載されたコードが実行される。 呼び出し側はこんな感じ。

let awesome = AwesomeView.instantiateFromNibWithOwner(nil, options: nil)

AwesomeViewのインスタンスを作っていることが一発でわかるし、

let awesome = UINib(nibName: "AwesomeView", bundle: nil).instantiateWithOwner(nil, options: nil).first as! AwesomeView

これと比べると随分治安がよくなったのではないだろうか。

このprotocol extensionの実装は、xibのファイル名が「クラス名.xib」の時だけ有効である。もし何らかの理由でクラス名とxib名が違うのであれば(リネームしろよ感はあるものの)、以下のように

final class AwesomeView: UIView, NibInstantiatable {
  static var nibName = "SuperAwesomeView"
}

nibNameだけ自分のクラスに書いてやれば、protocol extensionではなくこっちが優先されるのでこれも対応が楽。

iOSDCに参加した #iosdc

会場が練馬って朝知ったので遅刻した。

ベストトーク賞とられたReactive Programmingの話。

メモリ管理の話。

デザイナーにStoryboard使ってもらう話。

デバッグの話。

余談

追記

対応ありがとうございます。

さらに追記

うるせえ!!!!!!!!!!!!!!!!!!!!!!!

NSTextFieldをAutoLayoutで配置すると幅が0になっちゃう

最近趣味プログラミングでAppKitを触っている。

AppKitについて言いたいことはいろいろあるがUILabel代わりに使ったNSTextFieldについてハマったので書いておく。

Slackのメッセージみたいな見た目のビューを作りたくてAutoLayoutでごちゃごちゃ配置してたらNSTextFieldの幅がゼロになってしまって困った。
目指していた形はこんなの。

f:id:hiragram:20160818223139p:plain

で、当初ふつうに配置して制約かけたら

f:id:hiragram:20160818223944p:plain

こんなんなった。Xcodeのビューの階層見れるアレ(名前知らん)で見たらこんなん。

f:id:hiragram:20160818224014p:plain

いる。

幅0のNSTextField2つと、なんか中途半端に生き残ったNSTextFieldがいる。

最初に疑ったのはAutoLayoutで、自分で書けた制約のプライオリティとか、Content Compression Resistance Priorityなんかを気にしていろいろいじってみたが全然改善されず、1時間くらい浪費した。

Content Compression Resistance Priorityについて調べなおしていたら、intrinsicContentSizeにたどり着いた。

Custom views typically have content that they display of which the layout system is unaware. Setting this property allows a custom view to communicate to the layout system what size it would like to be based on its content. This intrinsic size must be independent of the content frame, because there’s no way to dynamically communicate a changed width to the layout system based on a changed height, for example.

つまりContent Compression Resistance Priorityで登場するビューの固有サイズというのはこいつのことらしい。かけられた制約が固有サイズより大きかった時の固有サイズの優先度がContents Hugging Priority、逆に制約が固有サイズより小さかった時の固有サイズの優先度がContents Compression Resistance Priorityということだ。

で、stringValueをセットしたあとのNSTextFieldのintrinsicContentSizeを見ると

自分でかけた制約に問題はなく、Content Compression Resistance Priorityにも問題はなく(そもそも触ってない)、AutoLayoutやめてsizeToFit()でサイズ決めて配置したとしてもintrinsicContentSizeのwidthは-1.0なので見えないままでしたよ、という結構きついハマりだった。

対応めんどくさかったら投げ出して帰るつもりだったが、StackOverflowにまさにその回答があった。

-[NSTextField intrinsicContentSize] always has undefined width - StackOverflow

この一番voteを稼いでる回答(というか質問者が自己解決したっぽい)によると、

OK, finally figured it out…

[_textfield setEditable:NO]

That's it. I guess that with an editable textfield one must have an explicit constraint for the textfield width. Which kind of makes sense, imagine editing a textfield and it would constantly grow horizontally with every keystroke... not an ideal UI.

僕はSwiftなので

nameLabel.editable = false

これでいけた。他のNSTextFieldも同じ。

Content Compression Resistance Priorityが具体的にどう動くのかを把握してなかったので時間くってしまった。

AutoLayoutに関して僕が良いなと思ったおすすめの本はこちら。

よくわかるAuto Layout  iOSレスポンシブデザインをマスター

よくわかるAuto Layout iOSレスポンシブデザインをマスター

いやーはまったはまった。