iOS開發中、如何選擇重寫初始化方法?

天地之仁,故萬物皆屌絲

說在前面的話

很多時候,我們總是需要重寫父類的方法達到我們開發需求。那么在iOS開發過程中,會有哪些初始化方法呢?又應該注意些什么問題?比如在一個自定義的UIView子視圖中, 如果重寫了initWithFrame:方法, 為什么不應該再重寫init方法。

可以先下載項目HGInitManager

強調

  • 1、只談現象,不談實質原理。
  • 2、重點在乎的是如何選擇,而不是對與錯。

類方法

+ (instancetype)new;

對于這個方法,具體的原理可以簡寫如下:

+ (instancetype)new {
    return [[self alloc] init];;
}

在我們的開發中,這里理解完全是沒有問題的。但是實質上的實現可以參考 源代碼 objc4-723 & NSObject.mm , 在 NSObject.mm 文件中可以找到這個方法的地方:

image.png

callAlloc 函數的實現就在當前的文件:

image.png

實例方法

在介紹實例的初始化方法的時候,我將采取 MVC 的設計模式向大家介紹。

Model 層

Model層一般都叫模型類:特指直接或間接繼承NSObject的類。主要是把 UIView 與 UIViewController 區別開,當然UIView 與 UIViewController 也是集成于NSObject。

init

這個方法是所有OC對象類都有的方法,所以所有的對象類都可以重寫。重寫樣例如下:

- (instancetype)init {
    self = [super init];
    
    // TODO: 給當前self做默認設置
    
    return self;
}

回頭看看本文的標題<iOS開發中、如何選擇重寫初始化方法?>。那么問題來了,這個方法對所有的對象類都通用,我們在什么時候重寫才顯得更高大上呢?我的建議是:僅僅在模型類(Model)中,才去重寫。
例子:

- (instancetype)init {
    self = [super init];
    
    // 設置默認年齡是 18
    self.age = 18;
    
    return self;
}

對,你沒有看錯。僅僅在 Model 中對數據的默認設置的時候,才有必要重寫這個方法。

聰明的你會問,在 UIView 與 UIViewController 中就不可以重寫這個方法么?請回到我強調的第二點(2、重點在乎的是如何選擇,而不是對與錯。)。

在這里我想先說一下的是:在 UIView 與 UIViewController 中堅決不要重寫 init 方法。在接下來會介紹在 UIView 與 UIViewController 中應該如何選擇重寫 初始化方法。

View 層

在這里要清楚的一點是,在 View 層有兩種方式加載 View :純代碼與XIB(SB)。這里要區分來介紹,因為這兩種方式的初始化方法是兩套機制。

純代碼

大家應都知道,純代碼創建 View 有兩個方法提供選擇,分別是:

- (instancetype)init
{
    self = [super init];
    
    // TODO: 給當前視圖做默認設置
    
    NSLog(@" init ");
    
    return self;
}

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    
    // TODO: 給當前視圖做默認設置
    
    NSLog(@" initWithFrame: ");
    
    return self;
}

那么問題又來了,這兩個方法在 View 中,我們應該如何選擇呢?兩個都要重寫,還是只要重寫其中一個?帶著這個問題,我們創建一個繼承于 UIView 的HGInitView。并且將上面的代碼copy到 .m 文件中。

我們在其它地方用來年各種功能方式創建:
方式1:

HGInitView* hgView = [[HGInitView alloc] initWithFrame:CGRectZero];

打印日志如下:

initWithFrame:

打印結果 證明: 僅僅觸發了 initWithFrame: 方法。這結果其實沒有什么可驚訝的,我們再來看看方式2。

方式2:

HGInitView* hgView = [[HGInitView alloc] init];

打印日志如下:

0.png

打印結果 證明: 即觸發了 initWithFrame: 方法 又觸發了init 方法。問題來了,如果我用方式2來創建視圖的話,不久重復了么?

最終結論:

  • 兩種方式都能正確的創建試圖。
  • 兩種方式都會觸發initWithFrame: 方法。

所以在這里的建議是:在 UIView 的子類中不要重寫 init 方法,重寫 initWithFrame: 方法就足夠了。

XIB

也有兩種方式給當前的試圖做初始化。分別是:


- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    
    // TODO: 給當前視圖做默認設置
    self.hgLabel.textColor = [UIColor redColor];
    
    NSLog(@"initWithCoder:");
    
    return self;
}

- (void)awakeFromNib {
    [super awakeFromNib];
    
    // TODO: 給當前視圖做默認設置
    self.hgLabel.textColor = [UIColor redColor];
    
    NSLog(@"awakeFromNib");
    
}

在這里只有一種方式來加載視圖,代碼如下:

NSArray* views =  [[NSBundle mainBundle] loadNibNamed:@"HGInitView" owner:nil options:nil];
    HGInitView* hgView = views.firstObject;

打印日志如下:

XIB 文件加載視圖

同樣兩個方法都調用了,并且一切都很正?!,F在我有目的的將 awakeFromNib 方法注釋,看看效果。很驚訝的發現‘self.hgLabel.textColor = [UIColor redColor];’這句代碼沒有效果,這是為什么呢?請看下圖:

解惑

答案就在上圖中,當執行 initWithCoder: 方法的時候,當前試圖中的子試圖還沒有值。所以在個方法中給子試圖設置默認效果是無效的。這里只能給其它的屬性做默認設置。

這里就不用看只打開 awakeFromNib方法的效果了。在這個方法中,什么都有,什么都可以設置了。

這里也提醒大家:initWithCoder: 方法一般是用于資源文件加載用,一般用于 解檔 時用到。一般情況要慎用。

綜上所屬: 在XIB視圖加載中,一般重寫awakeFromNib方法就可以了。

View 層總結:

  • 純代碼創建試圖,重寫 initWithFrame: 方法就足夠。
  • XIB 加載視圖,重寫awakeFromNib方法就足夠。

如果當前視圖可能用于純代碼,也可能用于XIB 加載,一般是在封裝控件的時候比較多。那么按照下圖中的寫,就夠了:

View 層通用

Controller 層

同樣 Controller 也有兩種方式加載。與之相關的方法如下:


- (instancetype)init {
    self = [super init];
    
    // TODO: 默認設置
    
    return self;
}

- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    
    // TODO: 默認設置
    self.hgLabel.textColor = [UIColor redColor];
    
    return self;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // TODO: 默認設置
    self.hgLabel.textColor = [UIColor redColor];
}

在這里,我就不帶著大家一一分析了。直接把我的建議分享給大家,同時也是希望大家動手試試。在這里要強調一點的是 viewDidLoad 說成是初始化方法,真的也有點搞笑 。但是希望你先把我的建議看完,再好好的笑一笑。

笑一笑

我的建議是:

  • 1、直接在 viewDidLoad 方法中直接對所有的默認做設置。
  • 2、在initWithNibName: bundle:方法做非視圖的默認設置,在viewDidLoad 方法中做與視圖有關的默認設置。
  • 言之總而:在init開頭的方法中,不要做與視圖有關的設置。

感謝看到這里的小伙伴!到這里,本文就算是結束了,有什么問題,歡迎大家討教與騷擾。

也可以下載項目HGInitManager

以下介紹添加于 2018-09 《兩個重要的宏介紹》

NS_DESIGNATED_INITIALIZER: Designed initializer 與 NS_UNAVAILABLE

NS_DESIGNATED_INITIALIZER 是用于指定初始化方法,NS_UNAVAILABLE 則是用禁用其標記的初始化方法。有一個簡單的例子:

// .h 文件
#import <Foundation/Foundation.h>

@interface HGObject : NSObject

- (instancetype)initWithName:(NSString*)name;
// NS_DESIGNATED_INITIALIZER: Designed initializer
- (instancetype)initWithName:(NSString*)name age:(NSInteger)age NS_DESIGNATED_INITIALIZER;

// NS_UNAVAILABLE: 禁止調用
- (void)hgPrint NS_UNAVAILABLE;

@end

// .m 文件
#import "HGObject.h"

@interface HGObject () {
    NSString* _name;
    NSInteger _age;
}

@end

@implementation HGObject

- (instancetype)init {
    return [self initWithName:@"CoderHG"];
}

- (instancetype)initWithName:(NSString *)name {
    return [self initWithName:name age:18];
}

- (instancetype)initWithName:(NSString *)name age:(NSInteger)age {
    self = [super init];
    _name = name.copy;
    _age = age;
    return self;
}

// 打印
- (void)hgPrint {
    NSLog(@"姓名: %@  年齡: %zd", _name, _age);
}

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

推薦閱讀更多精彩內容

  • http://www.cocoachina.com/cms/wap.php?action=article&id=1...
    Kevin追夢先生閱讀 1,068評論 0 3
  • 1.自定義控件 a.繼承某個控件 b.重寫initWithFrame方法可以設置一些它的屬性 c.在layouts...
    圍繞的城閱讀 3,454評論 2 4
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結起來就是把...
    Dove_iOS閱讀 27,205評論 30 471
  • 夜空湛藍 黑色里是我不曾見過的 那種五彩的明亮 看那顆天邊最遠的星辰 是璀璨的虹 星空深邃 在那靈魂深處 有一種動...
    云吉閱讀 264評論 2 0
  • 2017年10月13日,涵德好父母沙龍的主題是打造您的學習型家庭,由謝君教練主講。 您認為什...
    安靜131閱讀 486評論 0 0