もっとnibを深く知る

nib、よくわからないまま使っていませんか？
そんなnibのこと、知ってみませんか？

# `Nib` と `Xib` って何が違うの？

nib = `NeXT Interface Builder` のアクロニム。Interface Builder version 3 以前では拡張子 `.nib` が使われていたらしい。
xib = Interface Builder version 3 で追加されたファイル形式。フラットなファイル(XML)なので `xib`　。

らしい。
ソースは
https://stackoverflow.com/questions/3726400/what-is-the-difference-between-nib-and-xib-interface-builder-file-formats
https://en.wikipedia.org/wiki/Interface_Builder#Design

# ロードするとき `Bundle` と `UINib` のどっち使えばいいの？

```swift
class HogeView: UIView {

    // ①
    func instantiateFromBundle() {
        let bundle = Bundle(for: type(of: self))
        let view = bundle.loadNibNamed("HogeView", owner: self, options: nil)!.first as! UIView
        ...
    }

    // ②
    func instantiateFromNib() {
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: "HogeView", bundle: bundle)
        let view = nib.instantiate(withOwner: self, options: nil).first as! UIView
        ...
    }
}
```

①も②も、動作するコードです。
`NSBundle` ( `Bundle` )を使うこともできるし、 `UINib` からロードすることもできる。一体何が違うのかというと…
**何度もインスタンス化するなら `UINib` を使いましょう。** とドキュメントに明記されています。

https://developer.apple.com/reference/uikit/uinib

>A UINib object caches the contents of a nib file in memory, ready for unarchiving and instantiation. When your application needs to instantiate the contents of the nib file it can do so without having to load the data from the nib file first, improving performance. (...) **Your application should use UINib objects whenever it needs to repeatedly instantiate the same nib data.**

「 `UINib` オブジェクトはコンテンツをメモリキャッシュし、unarchivingとインスタンス化に備えます。nibを何度もインスタンス化するのであれば、 `UINib` を使うべきです」（ざっくり訳）とのことです。 `NSBundle` を使う場合、毎回ディスクに読みにいってしまうのでオーバーヘッドがあるけれど、それを一回で済ませてメモリキャッシュしてくれるのが `UINib` の役割なのですね。

つまり先述のコード例では、**`bundle.loadNibNamed(...)` しているほうはパフォーマンス的に良くないコードです。
避けましょう。**

# nib のロード時に `owner: self` 渡してるけど、これって何やってるの？

初めて見るときには、奇妙に感じますよね。このコード。

```swift
final class HogeView: UIView {

    @IBOutlet weak var label: UILabel!

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)!
        instantiateFromNib()
    }

    func instantiateFromNib() {
        let nib = UINib(nibName: "HogeView", bundle: Bundle(for: type(of: self)))
        let view = nib.instantiate(withOwner: self, options: nil).first as! UIView
        // 👆 なんで `self` を渡してるの？

        self.addSubview(view)
        // 👆 なんで `addSubview` しなきゃいけないの？

        view.translatesAutoresizingMaskIntoConstraints = false
        view.topAnchor.constraint(equalTo: topAnchor).isActive = true
        view.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
        view.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
        view.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
}
```

ソースコメントで示した疑問のとおりですね。
この理由を確認するために、nibのローディングプロセスを細かく見てみましょうか。

https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/LoadingResources/CocoaNibs/CocoaNibs.html#//apple_ref/doc/uid/10000051i-CH4-SW24 によると、nibのローディングプロセスは以下のような感じです（iOSの場合。Cocoaだけに関わる部分は削ってます）。

1. nibファイルのコンテンツ（nibのオブジェクトグラフ）をメモリへとロードする。ただしこの段階ではunarchiveはしない。
2. nibのオブジェクトグラフをunarchiveし、インスタンス化する。オブジェクトは `initWithCoder:` メッセージを受けとる。
3. コネクションを再構築する。再構築対象には、placeholder objects(iOSの文脈では `File's Owner` のこと)とのコネクションを含む。
	- Outletコネクションは、 `setValue:forKey:` で接続される。KVO通知が走る。
	- Actionコネクションは、 `addTarget:action:forControlEvents:` で接続される。もしtargetがnilのとき、actionはresponder chainによって扱われる。
4. `awakeFromNib` が呼ばれる。呼ばれるのはnib-loading codeでインスタンス化されたものに対してだけで、 `File's Owner` や `First Responder` のものは呼ばれない。

`1.` でオブジェクトグラフがロードされた時点では、それは不完全な存在です。なぜなら、 `File's Owner` とのコネクションもあるのに、その `File's Owner` がまだ外部から与えられていないからです。
`File's Owner` として `HogeView` を扱う場合、そのコネクションが構築されるタイミングは、 `3.` です。
つまり、 `HogeView` は `3.` より前のタイミングでインスタンス化されてないといけないし、 `HogeView` と `3.` の結果得られるViewインスタンスは別物にならざるを得ない。
だから、 `addSubview` が必要なのです。

# `nib.instantiate` の引数 `options` ってnil以外に何を渡せるの？

ディクショナリのキーとしては [`UINibExternalObjects`](https://developer.apple.com/documentation/uikit/uinibexternalobjects) が唯一用意されています。
これを何に使うのかですが……
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/LoadingResources/CocoaNibs/CocoaNibs.html#//apple_ref/doc/uid/10000051i-CH4-SW24
には、次のようなコード例が載っています。

```objc
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
    NSArray*    topLevelObjs = nil;
    NSDictionary*    proxies = [NSDictionary dictionaryWithObject:self forKey:@"AppDelegate"];
    NSDictionary*    options = [NSDictionary dictionaryWithObject:proxies forKey:UINibExternalObjects];
 
    topLevelObjs = [[NSBundle mainBundle] loadNibNamed:@"Main" owner:self options:options];
    if ([topLevelObjs count] == 0)
    {
        NSLog(@"Warning! Could not load myNib file.\n");
        return;
    }
 
    // Show window
    [window makeKeyAndVisible];
}
```

ですがこれはもはや使われない使用例ですし、ほかに効果的な使いどころがあるのかというと、自分には思いつきません。
誰かエレガントに `UINibExternalObjects` を使いこなしている御仁はいらっしゃるのでしょうか。いたら是非、使い方教えてください。

と逆に問いかけつつ、本稿を締めます。