說在前面的話
很多時候,我們總是需要重寫父類的方法達到我們開發需求。那么在iOS開發過程中,會有哪些初始化方法呢?又應該注意些什么問題?比如在一個自定義的UIView子視圖中, 如果重寫了initWithFrame:方法, 為什么不應該再重寫init方法。
可以先下載項目HGInitManager
強調
- 1、只談現象,不談實質原理。
- 2、重點在乎的是如何選擇,而不是對與錯。
類方法
+ (instancetype)new;
對于這個方法,具體的原理可以簡寫如下:
+ (instancetype)new {
return [[self alloc] init];;
}
在我們的開發中,這里理解完全是沒有問題的。但是實質上的實現可以參考 源代碼 objc4-723 & NSObject.mm , 在 NSObject.mm 文件中可以找到這個方法的地方:
callAlloc 函數的實現就在當前的文件:
實例方法
在介紹實例的初始化方法的時候,我將采取 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: 方法。這結果其實沒有什么可驚訝的,我們再來看看方式2。
方式2:
HGInitView* hgView = [[HGInitView alloc] init];
打印日志如下:
打印結果 證明: 即觸發了 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;
打印日志如下:
同樣兩個方法都調用了,并且一切都很正?!,F在我有目的的將 awakeFromNib 方法注釋,看看效果。很驚訝的發現‘self.hgLabel.textColor = [UIColor redColor];’這句代碼沒有效果,這是為什么呢?請看下圖:
答案就在上圖中,當執行 initWithCoder: 方法的時候,當前試圖中的子試圖還沒有值。所以在個方法中給子試圖設置默認效果是無效的。這里只能給其它的屬性做默認設置。
這里就不用看只打開 awakeFromNib方法的效果了。在這個方法中,什么都有,什么都可以設置了。
這里也提醒大家:initWithCoder: 方法一般是用于資源文件加載用,一般用于 解檔 時用到。一般情況要慎用。
綜上所屬: 在XIB視圖加載中,一般重寫awakeFromNib方法就可以了。
View 層總結:
- 純代碼創建試圖,重寫 initWithFrame: 方法就足夠。
- XIB 加載視圖,重寫awakeFromNib方法就足夠。
如果當前視圖可能用于純代碼,也可能用于XIB 加載,一般是在封裝控件的時候比較多。那么按照下圖中的寫,就夠了:
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