前言

为什么不要在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的博客,希望此文能详尽大家的疑惑,未尽之处请留言指正。

为什么不能在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还没有初始化完成。

案例二

如果在父类的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已经被调用了,基于其完整的假设已经不成立,那么再执行子类的代码会存在一定风险,如上例就会崩溃。

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

结论

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

结尾

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

文/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方法中不要调用属性的存取方法,而要直接调用 _实例变量

为什么不能在init和dealloc函数中使用accessor方法的更多相关文章

  1. 不要在init和dealloc函数中使用accessor

    不要在init和dealloc函数中使用accessor 文章目录 Objective-C 2.0 增加了 dot syntax,用于简单地调用成员变量的 accessor.相当于 java 的 ge ...

  2. C++程序设计(关于函数中数组传递的一点心得)

    题目: 10个学生考完期末考试评卷完成后,老师需要划出及格线,要求如下: (1) 及格线是10的倍数: (2) 保证至少有60%的学生及格: (3) 如果所有的学生都高于60分,则及格线为60分:   ...

  3. Flask中无法在其他函数中查询Sqlachemy的解决办法

    报错信息部分截取: File "D:\python 3.5\lib\site-packages\flask_sqlalchemy\__init__.py", line 912, i ...

  4. tp5模型事件回调函数中不能使用$this

    tp5模型事件回调函数中不能使用$this,使用会报错,涉及到数据库操作使用Db类,不能使用$this->save()之类的方式 如果回调函数中需要使用类内函数,需要将函数定义为static,通 ...

  5. 传说用户发来的请求是在JIoEndpoint的accept函数中接收的,是tomact与外界交互的分界点

    传说用户发来的请求是在JIoEndpoint的accept函数中接收的, 这是tomact与外界交互的分界点,所以来研究一下, >>>>>>>>> ...

  6. vue中使用Ajax(axios)、vue函数中this指向问题

    Vue.js 2.0 版本推荐使用 axios 来完成 ajax 请求.Axios 是一个基于 Promise 的 HTTP 库,可以用在浏览器和 node.js 中. axios中文文档库:http ...

  7. ES6 箭头函数中的 this?你可能想多了(翻译)

    箭头函数=>无疑是ES6中最受关注的一个新特性了,通过它可以简写 function 函数表达式,你也可以在各种提及箭头函数的地方看到这样的观点——“=> 就是一个新的 function”. ...

  8. js函数中this的不同含义

    1.js函数调用过程中,js线程会进入新的执行环境并创建该环境的变量对象,并添加两个变量:this和arguments,因此可以在函数中使用这两个变量.需要注意的是,this变量不能重新赋值,而arg ...

  9. c++中函数中变量内存分配以及返回指针、引用类型的思考

    众所周知,我们在编程的时候经常会在函数中声明局部变量(包括普通类型的变量.指针.引用等等). 同时,为了满足程序功能的需要,函数的返回值也经常是指针类型或是引用类型,而这返回的指针或是引用也经常指向函 ...

随机推荐

  1. struts2类型转换

    1. Struts2中的类型转换 我们知道通过HTTP提交到后台的数据,都是字符串的形式,而我们需要的数据类型当然不只字符串类型一种.所以,我们需要类型转换! 在Struts2中,类型转换的概念除了用 ...

  2. Java的Debug调试

    一.在项目上右键,Debug As>Debug on Server 二.在测试类上,Run As>Run On Server

  3. python征程3.1(列表,迭代,函数,dic,set,的简单应用)

    1.列表的切片. 1.对list进行切片.'''name=["wangshuai","wangchuan","wangjingliang", ...

  4. 【Java学习系列】第2课--Java语法及面向对象

    本文地址 分享提纲: 1. Java程序特点 1.1 基本语法 1.2 字符串 1.3 变量 1.4 Java数组 1.5 Java枚举 1.6 Java修饰符 1.7 Java编译制定在制定目录 2 ...

  5. Web测试介绍2一 安全测试

            安全测试是在IT软件产品的生命周期中,特别是产品开发基本完成到发布阶段,对产品进行检验以验证产品符合安全需求定义和产品质量标准的过程. 主要安全需求包括: (i) 认证 Authent ...

  6. MvcPager使用的Demo(同步分页)

    最近接触了一下MvcPager,昂...来做个笔记吧 其实,我喜欢前后端分离,分页这种东西前端负责的地方,后端不用顾问,这里的MvcPager有点让我想起服务器控件,毕竟用到了HtmlHelper. ...

  7. 基于rem的移动端自适应解决方案

    代码有更新,最好直接查看github: https://github.com/finance-sh/adaptive adaptivejs原理: 利用rem布局,根据公式 html元素字体大小 = d ...

  8. app使用微信支付成功后,点击返回到该app却跳到另外一个app去了

    刚接手了公司iOS的两个APP, 现在碰到了这样一个问题: 有一台iPhone在一个APP中使用了微信支付,支付成功后,点击返回到该APP,结果却跳到了另外一个APP去了. 这两个APP都是公司开发的 ...

  9. Action.c(58): Error -27796: Failed to connect to server "hostname"

    分析: 因为负载生成器的性能太好发数据特别快,服务器响应也特别快,从而导致负载生成器的端口在没有timeout之前就全部占满了. 解决方案一:   在负载生成器的注册表HKEY_LOCAL_MACHI ...

  10. freeswitch模块之event_socket

    这是我之前整理的关于freeswitch mod_event_socket的相关内容,这里记录下,也方便我以后查阅. mod_event_socket以socket的形式,对外提供控制FS一种途径, ...