initWithCoder:&awakeFromNib&initWithFrame:&init&initWithNibName:bundle:&loadNibNamed的作用及比較

很多朋友如果是初學iOS開發,可能會被其中的幾個加載方法給搞得暈頭轉向的,但是這幾個方法又是作為iOS程序員必須要我們掌握的方法,下面我將對這幾個方法做一下分析和對比,看看能不能增加大家對幾個方法的理解和使用.

首先是常用的加載方法有:

  • initWithNibName:bundle: (加載帶有XIB的控制器)
  • loadView (控制器的View為空的時候調用,幫控制器加載View)
  • initWithCoder: (是當從nib文件中加載對象的時候會調用)
  • awakeFromNib (當.nib文件被加載的時候,會發送一個awakeFromNib的消息到.nib文件中的每個對象)
  • initWithFrame: (代碼創建View時調用,是懶加載,只有到需要顯示時,子控件才不是 nil)
  • init(代碼使用創建控件alloc init 時,系統底層調用init方法)

首先我們開始從控制器(viewController)的加載開始說起:
??我們加載控制器可以使用代碼也可以使用storyboard

----NO1. 對于使用代碼加載控制器(這里是創建控制器哦,不是創建UIView,關于創建UIView另外再談),這里的ViewController帶有一個xib(storyboard,系統默認加載的,相當于顯示的是控制器的view)

ViewControllerWith *vc = [[ViewControllerWith alloc] init];

在這個加載過程中,相關方法調用順序是:

  • init
  • initWithNibName:bundle: - 加載帶有xib的控制器(默認ViewController)
  • loadView - 加載控制器視圖
  • viewDidLoad - 加載完畢

----NO2. 上面講了加載storyboard中的控制器,我們現在加載自定義控制器并且帶有xib的情況(也就是當我們Command + N,勾選also create XIB file的情況)

注意哦:看看后綴名,第一種情況是加載在storyboard中的控制器(默認是main.storyboard),現在講的是后綴.xib的控制器,兩個概念哈 - 我們系統默認加載是main.storyboard中的xib,區別是里面都是控制器,可以設置箭頭

圖1
圖2
圖3

看看上面的加載控制器的圖片,加載控制器可以設置箭頭的哦,箭頭的設置表示默認加載箭頭指向的的控制器,但是我們現在講的這種情況是加載自定義控制器的xib的情況(這種xib其實是UIVIew表示使用xib中的View去代替控制器的view來顯示,看圖):


圖4
圖5

在這里圖1-3和圖4-5一個是storyboard中一個是xib中,前者是控制器,后者本質是UIView,使用xib表示UIView去顯示控制器的view,兩者之間有區別的哦不要搞錯了

繼續說第二種情況,對于第二種情況的控制器加載是:

BackViewController *backVC = [[BackViewController alloc] initWithNibName:@"BackViewController" bundle:nil];

在加載過程中,相關方法調用順序是:

  • initWithNibName:bundle:
  • loadView
  • viewDidLoad

對于上面的這種情況,我專門做了一個小例子,看代碼:
在這里我使用純代碼:就是不使用系統默認加載的main.storyboard中的控制器,自己加載想要加載的控制器(OC程序員必備技能)
首先是去掉main.storyboard中的控制器,去掉viewController:自定義控制器并且加載它

圖6
AppDelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    JNTestViewController *vc = [[JNTestViewController alloc] initWithNibName:@"jtest" bundle:nil];
    self.window.rootViewController = vc;
    [self.window makeKeyAndVisible];
    return YES;
}

在這里我自定義控制器,并且將之設置為window的根控制器,當然我使用(initWithNibName)去加載我指定文件(控制器和view)
注意:這里關于xib的加載,我們要知道,我在這里是將xib文件命名為jtest,但是一般來說,創建自定義控制器的時候xib的命名是和自定義控制器是同名的,如果xib和自定義控制器同名,那么此時我們就可以直接init創建不需要指定xib的名字:

JNTestViewController *vc = [[JNTestViewController alloc] init];

可以這樣做的原因是,系統在底層首先調用init方法,在init方法內部自動會調用(initWithNibName)方法,首先系統先看看是否有指定名字的xib,如果沒有就加載控制器同名但是去掉Controller的xib,還沒有就加載與控制器同名的xib

----NO3. storyboard中加載控制器使用的相關方法
前面說了,在后綴名是storyboard中加載的是控制器,可以設置箭頭來指定默認想要系統去加載的控制器,那么如果使用代碼區加載想要加載的控制器呢,看代碼:
首先在Main.storyboard中去掉viewController的箭頭,添加id,如圖:

圖7

在指定了id之后,根據id加載指定的控制器,看代碼:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    //1.創建窗口
    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
     UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];//nil表示從mainBundle中取資源
    ViewController *vc = [storyboard instantiateViewControllerWithIdentifier:@"jntest2"];
    self.window.rootViewController = vc;
    [self.window makeKeyAndVisible];
    return YES;
}

依舊是使用純代碼,這種做法首先是指定想要加載的storyboard中的控制器的id,然后根據id加載指定的控制器,關鍵是(instantiateViewControllerWithIdentifier)方法的使用
在這個加載過程中 相關方法調用順序是:

  • initWithCoder:
  • awakeFromNib
  • loadView(控制器的View的子控件也是在控制器實例化之后加載view)
  • viewDidLoad

OKay,在上面講了關于加載控制器,下面呢我們將要講講關于加載UIView調用的方法
----NO1.首先是純代碼加載:
在這個加載過程中 相關方法調用順序是:

  • init
  • initWithFrame:
  • 是init調用了initWithFrame:

----NO2. xib的方式加載UIView
實現自定義xib - MyView.xib,如下圖:

圖8

在控制器中加載自定義的xib,將xib顯示在控制器的view上,看代碼:

- (void)viewDidLoad {
    UIView *myViews = [[[NSBundle mainBundle] loadNibNamed:@"MyView" owner:self options:nil] lastObject];
     myViews.frame = CGRectMake(10, 10, 100, 100);
    [self.view addSubview:myViews];
}

在對控制器和view的加載有所了解之后,我們接著來談論幾個方法之間兩兩的區別

NO1. init和initWithFrame方法
首先當代碼創建控件時,會有init,此時會底層調用init方法,但是init又會在內部調用initWithFrame方法,總的來說,兩個方法中作用都是對控件進行創建,在實際開發中可以將控件的創建直接寫在initWithFrame方法即可

NO2. initWithFrame和initWithCoder方法
我們在創建UIVIew的時候,一般會使用兩種方式:一種是代碼,一種是拖控件(interface builder也就是使用nib文件的方式),我們時候拖控件的方式此時initWithFrame方法不會被調用,因為nib文件知道如何初始化該view(拖控件的時候已經定義好了長度高等屬性),使用拖控件的方式會調用initWithCoder方法,在該方法中可以重新定義我們在nib中已經設置的各項屬性
在使用代碼進行view的創建的時候需要注意:當我們創建UIView的子類的時候,我們使用initWithFrame方法實例化UIVIew,并且特別注意:如果在子類中重載initWithFrame方法,必須先調用父類的initWithFrame方法,否則會出現一些意想不到的問題,看看使用initWithFrame創建的一般代碼格式:

JNView.m:
#import "JNView.h"
@implementation JNView
- (instancetype)initWithFrame:(CGRect)frame{
    self = [super initWithFrame:frame];
   
    //在該方法中進行初始化設置
    
    return self;
}
@end

簡單點:initWithoder 是當從nib文件中加載對象的時候會調用;initWithFrame是初始化并返回一個新的視圖對象,在使用代碼創建對象的時候調用.由此:當我們在自定義控件的時候(使用代碼創建),一般的套路都是先在init或者initWithFrame方法中做子控件的創建和初始化,在layoutSubviews方法中進行子控件的布局,然后再重寫子控件的setter方法給子控件設置數據

NO3. initWithNibName 和 loadNibNamed 方法
我的理解是,使用initWithNibName時加載的是控制器,使用loadNibNamed時加載的是控件比如UIView,UIButton,并且由于在一個xib中可以有多個控件,所以該方法返回的是數組

NO4. initWithCoder:和awakeFromNib方法

  • initWithCoder: 只要對象是從文件(xib或storyboard)解析來的,就會調用
  • awakeFromNib 從xib或者storyboard加載完畢就會調用
  • 同時存在會先調用initWithCoder:

那么關于initWithCoder和awakeFromNib的關系:
首先調用initWithCoder加載xib或者storyboard,然后方法內部發送awakeFromNib消息給每個xib中的對象將對象喚醒,也就是說如果xib中手動拖拽了一個UIView在initWithCoder方法中該UIView是處于未被喚醒狀態,此時在initWithCoder方法中去手動向UIView的子控件添加控件,手動添加的控件不會被顯示
簡單點:就是說在initWithCoder方法中添加子控件是可以顯示,但是添加子控件的子控件不被顯示,再簡單點的話就是說以后想要手動添加控件在xib中請直接寫在awakeFromNib方法中

可能還有些朋友對這兩個方法的區別還是有點暈,沒關系,我做了一個例子,一起看看以上兩個方法在具體使用上的區別:

我自定義UIView并且關聯xib,看下圖:

圖9

提供一個類工廠方法快速創建該自定義UIView

  + (instancetype)uiview
{
    return [[[NSBundle mainBundle] loadNibNamed:@"JNUIView" owner:nil options:nil] lastObject];
}

匿名分類中拿到圖中拖拽的UIView,并且定義了兩個UILabel

@interface JNUIView()
@property (weak, nonatomic) IBOutlet UIView *cyView;
@property(weak, nonatomic) UILabel *JNLabel;
@property(weak, nonatomic) UILabel *JNLabel1;
@end

我先在initWithCoder中創建一個子控件(手動的哦),并且該控件是添加在當前的xib自身中不是子控件上

//創建子控件
-(instancetype)initWithCoder:(NSCoder *)aDecoder{
    if (self = [super initWithCoder:aDecoder]) {
        UILabel *label = [[UILabel alloc] init];
        label.backgroundColor = [UIColor blueColor];
        label.text = @"JN";
        [self addSubview:label];
        self.JNLabel = label;
        //-----------
        
    }
    return self;
}

結果如下:

圖10

我向子控件cyView中添加控件,看看結果

  //創建子控件
-(instancetype)initWithCoder:(NSCoder *)aDecoder{
    if (self = [super initWithCoder:aDecoder]) {
        UILabel *label = [[UILabel alloc] init];
        label.backgroundColor = [UIColor blueColor];
        label.text = @"JN";
        [self addSubview:label];
        self.JNLabel = label;
        //-----------
        UILabel *label1 = [[UILabel alloc] init];
        label1.backgroundColor = [UIColor yellowColor];
        label1.text = @"yellow";
        [self.cyView addSubview:label1];
        self.JNLabel1 = label1;
    }
    return self;
}
圖11

上面的結果顯示向子控件cyVIew中添加子控件是不行的,那么如果我非要向cyView中添加子控件該怎么辦? - 利用awakeFromNib,看代碼:

  -(void)awakeFromNib
{
    [super awakeFromNib];
    UILabel *label1 = [[UILabel alloc] init];
    label1.backgroundColor = [UIColor yellowColor];
    label1.text = @"yellow";
    [self.cyView addSubview:label1];
    self.JNLabel1 = label1;

}

結果如何,看下圖:

圖12

結果已經顯示了,利用awakeFromNib可以實現向xib的子控件中添加子控件,因為此時子控件已經被喚醒可以添加子控件的子控件的,總之記住:創建控件寫在awakeFromNib方法中就可以了

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容