为何不能在init和dealloc函数中使用accessor方法

前言

为何不要在init和dealloc方法中调用getter和setter:
Apple在Mac与iOS中关于内存管理的开发文档中,有一节的题目为:“Don’tUse Accessor Methods in Initializer Methods and dealloc”,文中说:“Theonly places you shouldn’t use accessor methods to set an instancevariable are in initializer methods anddealloc.”可是并无解释为何。网上搜索了几篇国内国外的文章和一些大V的博客,但愿此文能详尽你们的疑惑,未尽之处请留言指正。html

为何不能在init中调用accessor

案例一

下面这则代码说明了一种可能会引发错误的状况:现有两个类BaseClass和SubClass,SubClass继承自BaseClass。父类有一个value属性(子类天然也会集成该属性)。若是在父类的init(或其余初始化构造方法)中使用了value的setter,子类也重写了value的setter,那么就会出现问题。缘由以下:子类调用init(或其余初始化构造方法)初始化对象时候,子类的init会首先调用父类的init(self = [super init]),这样就会调到父类的init方法里,而咱们在父类的init方法里调用了setter给value属性赋值。父类会直接调用子类重写的那个setter(由于子类重写了value的setter)。此时,子类对象尚未初始化好,但子类value的setter先却先于子类本身的init代码调用(由于此时子类的init方法尚未return self),就有可能会出现问题。若是咱们在子类的setter方法中作了其余操做,好比修改了某个实例变量的值,那么就会出错,由于此时self尚未初始化好。
形成这个问题的缘由有两个:一就是在父类的init使用了setter;二是子类重写了setter,致使在父类init时就会调用子类重写的setter,万一重写的setter中进行了一些子类特有的操做就可能会出现问题,好比,给子类的某个属性赋值失败,由于此时子类对象self尚未初始化完成。linux

案例二

若是在父类的init方法中使用了value的setter,同时也在父类写了setter。当子类初始化时会先调用父类的init方法,即self = [super init],因为父类中使用了value的setter,那么父类的init又会调到value的setter,若是setter中作了其余的操做,好比发送一个网络请求,那么此时就有可能出现问题。而当子类对象经过setter给value赋值时,又会调用父类的setter。那么至关于父类的setter被调用了两次,发送了两次相同的网络请求。编程

init call accessor Example:安全

@interface BaseClass : NSObject @property(nonatomic) NSString* info; @end @implementation BaseClass - (instancetype)init { if ([super init]) { self.info = @"baseInfo"; } return self; } @end
@interface SubClass : BaseClass @end @interface SubClass () @property (nonatomic) NSString* subInfo; @end @implementation SubClass - (instancetype)init { if (self = [super init]) { self.subInfo = @"subInfo"; } return self; } - (void)setInfo:(NSString *)info { [super setInfo:info]; NSString* copyString = [NSString stringWithString:self.subInfo]; NSLog(@"%@",copyString); } @end

当执行[[SubClass alloc]init]时会调用父类在Init方法。其中调用了accessor,去初始化父类部分的info属性。看起来十分正常,但一旦子类重写了该方法,那么因为多态此时调用的就是子类的accessor方法!子类的accessor实现中的代码都是以子类部分已初始化彻底为前提编写,即子类部分已经初始化完毕,彻底可用,而现实状况是其init方法并无执行完,对此假设并不成立,从而可能形成崩溃。以上例子有人造的痕迹,现实中更多的是某个方法被少调用一次,出现逻辑错误。网络

为何不能在dealloc中调用accessor

仍是基于子类重写了父类的value属性这一前提,在子类对象销毁时,首先调用子类的dealloc,最后调用父类的dealloc(这与init初始化方法是相反的,且ARC中不须要咱们手动调用[super dealloc])。若是父类在dealloc中调用了value的accessor且该accessor被子类重写,就会调到子类的accessor。但此时子类已经释放(由于先调用子类的dealloc,后调用父类的dealloc),因此就会出现错误甚至崩溃。
dealloc call accessor example函数

@interface BaseClass : NSObject @property(nonatomic) NSString* info; @end - (void)dealloc { self.info = nil; } @end
@interface SubClass : BaseClass @property (nonatomic) NSString* debugInfo; @end @implementation SubClass - (instancetype)init { if (self = [super init]) { _debugInfo = @"This is SubClass"; } return self; } - (void)setInfo:(NSString *)info { NSLog(@"%@",[NSString stringWithString:self.debugInfo]); } - (void)dealloc { _debugInfo = nil; } @end

在SubClass的实例对象销毁时,首先调用子类的dealloc,再调用父类的dealloc(这与init初始化是相反的,且ARC中不须要咱们手动调用[super dealloc])。若是父类在dealloc时调用了accessor 而且该accessor被子类重写,就会调用到子类的accessor。而此时子类的dealloc已经被调用了,基于其完整的假设已经不成立,那么再执行子类的代码会存在必定风险,如上例就会崩溃。ui

另外,在《Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法》的第31条——在dealloc方法中只释放引用并解除监听一节文中,做者也提到了下面一段话:在dealloc里不要调用属性的存取方法,由于有人可能会覆写这些方法,并于其中作一些没法再回收阶段安全执行的操做(上面已经提到)。此外,属性可能正处于“键值观察”(Key-Value Observation,KVO)机制的监控之下,该属性的观察者(Observer)可能会在属性值改变时“保留”或使用这个即将回首的对象。这种作法会令运行期系统的状态彻底失调,从而致使一些莫名其妙的错误。atom

结论

综上,不能在init和dealloc中使用accessor的缘由是因为面向对象的继承、多态特性与accessor可能形成的反作用联合致使的。继承和多态致使在父类的实现中调用accessor可能致使调用到子类重写的accessor,而此时子类部分并未彻底初始化或已经销毁,致使原有的假设不成立,从而出现一系列的逻辑问题甚至崩溃。为了更清晰地阐述,如下分别从init和dealloc上举例说明。spa

结尾

在init和dealloc中使用accessor是存在风险的。但这并不表明百分之百的崩溃或者百分之百的错误。从目前的实验来看,当存在继承时,在init或者dealloc方法中使用accessor会存在很高的风险,此时咱们可要当心了。不过,在公司项目中,仍是建议你们不要铤而走险,即便如今代码没有问题,难保未来维护或扩展时会出现问题。只有将苹果所说的Don’t Use Accessor Methods in Initializer Methods and dealloc看成一条编程规范,才能从根本上规避这个问题。不过,有些状况咱们必须破例,必须访问accessor,好比:待初始化的实例变量声明在超类中,而咱们又没法在子类中访问此实例变量的话,那么咱们只能经过setter来对实例变量赋值。又好比:若是一个实例变量是lazy的(懒加载),这种状况必须经过getter方法访问属性,不然没法给实例变量赋值。
因此,万事无绝对,咱们只有理解了为何不能在init和dealloc方法中使用accessor才能在各类状况下游刃有余。.net

文/VV木公子(简书做者)
PS:如非特别说明,全部文章均为原创做品,著做权归做者全部,转载转载请联系做者得到受权,并注明出处,全部打赏均归本人全部!
若是您是iOS开发者,或者对本篇文章感兴趣,请关注本人,后续会更新更多相关文章!敬请期待!

参考文章

《Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法》
为何不要在init和dealloc函数中使用accessor
Objective-C, 为何不能在init或是dealloc方法中使用accessor方法
iOS中正确处理dealloc方法
为何不要在init和dealloc函数中使用accessor
初始化和dealloc方法中不要调用属性的存取方法,而要直接调用 _实例变量

相关文章
相关标签/搜索