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 函数的实现就在当前的文件:

iOS开发中、如何选择重写初始化方法?_第1张图片
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];

打印日志如下:

iOS开发中、如何选择重写初始化方法?_第2张图片
initWithFrame:

打印结果 证明: 仅仅触发了 initWithFrame: 方法。这结果其实没有什么可惊讶的,我们再来看看方式2。

方式2:

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

打印日志如下:

iOS开发中、如何选择重写初始化方法?_第3张图片
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;

打印日志如下:

iOS开发中、如何选择重写初始化方法?_第4张图片
XIB 文件加载视图

同样两个方法都调用了,并且一切都很正常。现在我有目的的将 awakeFromNib 方法注释,看看效果。很惊讶的发现‘self.hgLabel.textColor = [UIColor redColor];’这句代码没有效果,这是为什么呢?请看下图:

iOS开发中、如何选择重写初始化方法?_第5张图片
解惑

答案就在上图中,当执行 initWithCoder: 方法的时候,当前试图中的子试图还没有值。所以在个方法中给子试图设置默认效果是无效的。这里只能给其它的属性做默认设置。

这里就不用看只打开 awakeFromNib方法的效果了。在这个方法中,什么都有,什么都可以设置了。

这里也提醒大家:initWithCoder: 方法一般是用于资源文件加载用,一般用于 解档 时用到。一般情况要慎用。

综上所属: 在XIB视图加载中,一般重写awakeFromNib方法就可以了。

View 层总结:

  • 纯代码创建试图,重写 initWithFrame: 方法就足够。
  • XIB 加载视图,重写awakeFromNib方法就足够。

如果当前视图可能用于纯代码,也可能用于XIB 加载,一般是在封装控件的时候比较多。那么按照下图中的写,就够了:

iOS开发中、如何选择重写初始化方法?_第6张图片
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 

@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

你可能感兴趣的:(iOS开发中、如何选择重写初始化方法?)