初衷:本学习笔记的主要目的是我在学习过程中有些疑惑,说白了,就是不怎么懂的地方,用代码来验证,顺带做一些备忘。
Tip 7:在对象内部尽量直接访问实例变量
这条 tip 的要点是:
- 在对象内部读取数据时,应该直接通过实例变量来读取,而写入数据时,则应通过属性来写。
- 在初始化方法以及 dealloc 方法中,总是应该直接通过实例变量来读写数据。
- 有时会使用惰性初始化技术配置某份数据,这种情况下,需要通过属性来读取数据。
讲述第 2 点时,提到在初始化方法中不应该通过"设置方法"来设置属性值,不然如果子类 override 了设置方法,调用的将会是子类的设置方法。我对这点感到困惑。没想到这个小问题引发了一系列的其他问题,在探索这个疑惑的地方时,不小心趟了其他几个雷,而到此刻我明白了当初为什么看到这一段总是迷惑不解的原因了,这个地方的排版巧合以及翻译造成了我的迷惑,让我在心中混淆了 access method,我的中文和英文都不及格。书中的例子没有问题,唯一的困惑是在 set
我在尝试重写 set
时,使用了 KVC,结果发现调用的还是 set
,于是又递归了。复习了下 KVC,发现在《KVC Programming Guide》 里的小节「Commonly Used Accessor Patterns」下有这么一句话:
In order for an attribute or to-one relationship property to support setValue:forKey:, an accessor in the form set
: must be implemented.
另外看函数调用栈也看到了,之前以为两者没什么关系的。
另外,挖到一篇深入 KVC 的:《iOS 开发之你真的了解了 KVC 吗?》,跟我要查找的没多大关系,但可以看看。
在子类中无法直接访问父类的实例变量,如果要重写 setter 或 getter,只能调用自身,又要递归了。所以书中例子:
-(void)setLastName:(NSString *)lastName{
/*..do some thing..*/
self.lastName = lastName;
}
在实际代码中是不可行的。那么在子类中重写 setter 或 getter 要怎么办呢?最好不要。
update: (2015/04/17 20:12) 搜索了一番怎么在子类中重写 setter 方法,发现有人问过类似问题了,如果你想要在 setter 中做点其他的事,调用 super setter,这才是正确的 override。按理说,这么神的一本书应该不会有这种问题,难道我对此处的理解有问题?
到处挖的时候又挖到一篇《从一段奇葩的 objc 代码看代码规范的重要性》, 争论的是使用属性还是直接访问实例变量,该博客里的应用场景不太一样。这本来也在书中 tip 的讨论范围内,但没有细述。
结论是,在初始化方法中调用 setter 的话会有潜在的麻烦,官方文档中也直接说了「Access Instance Variables Directly from Initializer Methods」,这些文档我当初看过,不过跟昨天的博客里提到的一样,刚开始看没有实际代码经验很难体会。
另外,我初期的困惑在于为什么在父类的初始化方法里调用 setter 最终会调用子类的重写方法。实质是,向 self 方法消息时,到底会调用哪个类的方法?于是,挖到了下面的《刨根问底 Objective-C Runtime(1)- Self & Super》(好吧,该同学的链接总是这么拽,按标题找这篇博客吧),里面对下面这段话表示了一下质疑:
self 是类的隐藏参数,指向当前调用方法的这个类的实例。而 super 是一个 Magic Keyword, 它本质是一个编译器标示符,和 self 是指向的同一个消息接受者。上面的例子不管调用 [self class] 还是 [super class],接受消息的对象都是当前 Son *xxx 这个对象。而不同的是,super 是告诉编译器,调用 class 这个方法时,要去父类的方法,而不是本类里的。
当使用 self 调用方法时,会从当前类的方法列表中开始找,如果没有,就从父类中再找;而当使用 super 时,则从父类的方法列表中开始找。然后调用父类的这个方法。
然后展示了背后的 C 函数调用,老实说,C 函数我看晕了。但我觉得作者自身对 C 函数的解读不影响上面的论调。结论是,上面没错。另外,推荐博客里参考的文章《Understanding the Objective-C Runtime》。
回到我的困惑,如果子类重写了某个方法,那么该子类的实例调用该方法时,会使用自己重写的方法,在初始化方法里也一样。