LoginSignup
195
140

More than 5 years have passed since last update.

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

NibXib って何が違うの?

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

ロードするとき BundleUINib のどっち使えばいいの?

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 を使いましょう。 とドキュメントに明記されています。

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 渡してるけど、これって何やってるの?

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

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 OwnerFirst Responder のものは呼ばれない。

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

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

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

- (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 を使いこなしている御仁はいらっしゃるのでしょうか。いたら是非、使い方教えてください。

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

195
140
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
195
140