Skip to content

Instantly share code, notes, and snippets.

@Josscii
Last active March 10, 2017 09:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Josscii/cb700299ec8e4cf22af12a9344dd3bf2 to your computer and use it in GitHub Desktop.
Save Josscii/cb700299ec8e4cf22af12a9344dd3bf2 to your computer and use it in GitHub Desktop.
iOS 关于 xib 的那些事 (一)

Xib 是什么以及它的加载方式

在 iOS 开发中, 我们会经常接触到的 xib 文件, 还能听到它的另一个名字 nib, 其实它们俩差不多是指代同一个东西, 只不过 xib 是编译前, nib 是编译后, 还有后来的 storyboard, 它们其实都 xml 文件, 通过右键这些文件然后 open as > source code 就可以看到文件的源码.

如果你仔细比对 xib 和 storyboard 的 xml 的文件内容, 你会发现, 差别很小, 其中两个重要的差别是:

  • document type 不同: storyboard 的 type 是 com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB, 而 xib 的 type 是 com.apple.InterfaceBuilder3.CocoaTouch.XIB.
  • storyboard 相对于 xib 多了一个 scene 的 概念, 所有 xml 里会有一个顶层标签是 scenes 而 xib 里的顶层标签是 objects.

xib 和 storyboard 就像一个配置文件, 我们在图形化界面里将我们想要的界面搭建好, 然后我们调用系统提供的方法来读取这些文件来构建一个个对象.

下面我们来看看系统提供了怎样的方法来让我们完成这些操作.

最常用的就是从 xib 里面初始化 ViewController 了. 在我们创建 ViewController 的时候, Xcode 会询问我们是否创建一个 xib, 如果我们选择是, 那么和这个 ViewController 同名的 xib 将会被创建.这个时候, 我们可以调用 UIViewController 的下面这个初始化方法来初始化对象:

- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil;

通常来说我们并不需要传入 nibname 和 bundle, 因为 iOS 会为我们去找同名的 xib, 我们甚至可以直接调用无参的 init 方法, 因为默认的实现就是调用上面这个方法并传入 nil.

深入来看, 这个 xib 为我们设置了什么呢?

其中一个就是 File's Owner 会自动设置为对应的 ViewController 类, 并且该 ViewController 的 view 会和这个 xib 里面的 view 对象关联.

第二个用法就是, 我们常常从 xib 里加载 cell, 我们首先创建一个 xib, 然后用 tableview 注册这个 cell, 最后通过重用方法来初始化一个 cell 对象.

但是这个 xib 的 File's Owner 我们并没有设置.

这两个用法会带给我们一个疑问, 我们能够访问到 ViewController 的 view 和 cell 本身, 那么我们如果想拿到内部的 view 该如何操作呢?

系统为我们提供了 IBOutlet 这个概念, 在前一种用法中, 我们直接将想要引用的 view 拖一个 IBOutlet 的到相应的 ViewController, 这个 IBOutlet 就会和对象建立一个关联, 从某种角度看 ViewController 自带的 view 也像一个比较特殊的 IBOutlet. 对 cell 来说有所不同, 我们需要给它设定一个 custom class, 然后把相应的 IBOutlet 关联到那个类里面.

无论哪种方式, 系统在初始化 view 时都会调用下面的两个方法之一:

// UINib
- (NSArray *)instantiateWithOwner:(nullable id)ownerOrNil options:(nullable NSDictionary *)optionsOrNil;
// NSBundle
- (nullable NSArray *)loadNibNamed:(NSString *)name owner:(nullable id)owner options:(nullable NSDictionary *)options;

这两个方法返回的是 xib 里面所有的顶层对象, 如果你指定了 owner, 那么这两个方法会自动的将你连的 IBOutlet 关联, 也就是说, 你如果想从一个 xib 里面加载一个 view, 你完全可以连一个 IBOutlet 到 xib 的 file'owner, 然后在 viewDidLoad: 里调用上面的两个方法之一, 你的 IBOutlet 就会被自动赋值了, 就像这样:

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIButton *button;
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    
    [[NSBundle mainBundle] loadNibNamed:@"button" owner:self options:nil];
    
    // 到这里 _button 已经有值了
}
@end

当然你也可以直接给把这两个方法返回的顶层对象赋值给你想要的 view, 而不用 IBOutlet 这种方式。就像这样:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIbutton *button = [[NSBundle mainBundle] loadNibNamed:@"button" owner:nil options:nil].firstObject;
}

不仅如此, xib 里面不光可以是普通的 view, ViewController 也可以从 xib 初始化, 这里就不得不提到有关的几个初始化方法和回调了:

/// UIViewController
- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *);

/// NSCoding
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder;

/// NSObject(UINibLoadingAdditions)
- (void)awakeFromNib

我们知道的是, 一个 view 或 viewcontroller 的初始化, 如果是从 xib 或 storyboard 里面加载, 那么 initWithCoder:awakeFromNib 就会调用。

值得注意的是, UIViewController 的 initWithNibName:bundle: 并不是从 xib 里面加载 viewcontroller, 其实只是从 xib 加载了它的 main view, 也就是说 viewcontroller 的 initWithCoder:awakeFromNib 并不会调用。

其实 storyboard 就是采用 xib 加载各种对象的,包括 viewcontroller,所以只要从 storyboard 里面加载视图,那么无疑视图的 initWithCoder:awakeFromNib 都会被调用了。

还有一个常见的问题是 IBOutlet 什么时候会被赋值,答案是在 awakeFromNib 中才会有值。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment