说在前面的话
很多时候,我们总是需要重写父类的方法达到我们开发需求。那么在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;
打印日志如下:
同样两个方法都调用了,并且一切都很正常。现在我有目的的将 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
@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