effective OC2.0 52阅读笔记(二 对象、消息、运行期)

时间:2023-03-08 17:04:20

第二章:对象、消息、运行期

6 理解属性这一概念

总结:OC解决硬编码偏移量问题的做法,一种方案是把实例变量当做一种存储偏移量所用的特殊变量,交由类对象保管,偏移量会在运行期查找,叫做稳固的“应用程序二进制接口”ABI。二种方案是使用存取方法访问实例变量。属性的访问方法由编译器在编译期执行,并且编译器还会自动向类中添加实例变量。

eg:如果从core data的框架中的NSManagedObject类里继承一个子类,就需要在运行期动态创建存取方法。因为子类的某些属性不是实例变量,其数据来自后端的数据库中。

@interface EOCPerson:NSManagedObject

@property NSString *firstName;

@property NSString *lastName;

@end

@implementation EOCPerson

@dynamic firstName,lastName;//编译器在此时不会自动合成存取方法或实例变量。

@end

属性特质可以分为四类,原子性,读/写权限,内存管理语义(只要实现属性所用的对象是可变的,就应该在设置新属性值时拷贝一份) 方法名@property(nonatomic,getter=isOn) BOOL on;

实现自定义的初始化方法时,一定要遵循属性定义中宣称的“copy”语义,_first = [firstName copy];使用于别的内存管理语义。

注意:不应该在init方法中调用存取方法;应该尽可能使用不可变对象。

7 在对象内部尽量直接访问实例变量

总结:建议在读取实例变量的时候采用直接访问形式,而在设置实例变量的时候通过属性来做。

两种做法区别:直接访问实例变量速度快,编译器所生成的代码会直接访问保存对象实例变量的那块内存。但会绕过为相关属性定义的内存管理语义。不会触发键值观测。通过属性访问有助于排查相关错误。

通过设置方法写入实例变量时需要注意,在初始化方法中及dealloc方法中应该直接访问实例变量,对于初始化方法是因为子类可能会复写设置方法。在基类的默认初始化方法中,可能会将姓氏设为空字符串。此时若是通过设置方法来做,那么调用的将会是子类的设置方法,从而抛出异常。

- (void)setLastName:(NSString *)lastName

{

if(![lastName isEqualToString:@"smith"]){

[NSException raise:NSInvalidArgumentException format:@"Last name must be Smith"];

}

self.lastName = lastName;

}

另外:当要惰性初始化的时候,必须通过获取方法来访问属性,否则,实例变量就永远不会初始化。

8 理解“对象等同性”这一概念

总结:判断等同性的两个关键方法:- (BOOL)isEqual:(id)object; - (NSUInteger)hash;编写hash方法时,应该用当前的对象做做实验,以便在减少碰撞频度与降低运算复杂程度之间取舍。有时候判断等同性可以根据特有的标识符,就像主键一样。放入容器中的对象不应再改变,例如把某个对象放入set之后又修改其内容,那么后面的行为将很难预料。相同的对象必须具有相同的哈希码,但是两个哈希码相同的对象未必相同。不要盲目地逐个检测每条属性,而是应该依照具体需求来制定检测方案。编写hash方法时,应该使用计算速度快而且哈希码碰撞几率低的算法。

9 以类族模式隐藏实现细节

总结:类族模式可以把实现细节隐藏在一套简单的公共接口后面。工厂模式是创建类族的办法之一。Cocoa系统框架中大部分collection类都是类族。例如:用NSArray的alloc方法获取实例时,该方法首先会分配一个属于某类的实例,此实例充当“占位数组”。该数组稍后会转为另一个类的实例,而那个类则是NSArray的实体子类。当向类族中新增实体子类时,对于Employee这个例子来说,若是没有工厂方法的源代码,就无法向其中新增雇员类别了。然而对于NSArray这样的类族来说,还是有办法增加子类的,但是从类族的公共抽象积累中继承子类时要当心,若有开发文档,应先阅读。

10 在既有类中使用关联对象存放自定义数据

总结:有时需要在对象中存放相关信息,但是该类的实例可能是由某种机制所创建的,而我们无法令这种机制创建出自己缩写的子类实例。就要用关联对象(associated object)来解决这个问题。objc_setAssociatedObject(object,void *key,id value,objc_AssociationPolicy),objc_getAssociatedObject(object,key),objc_removeAssociatedObjects(object)。类似于NSDictionary,设置关联对象时用的键是不透明指针(opaque pointer),如果在两个键上调用isEqual方法的返回值是YES,那么NSDictionary就认为而这项等。然后在设置关联对象时,若想令两个键匹配到同一个值,则二者必须是完全相同的指针才行。所以设置关联对象值时,通常使用静态全局变量做键。只有在其他做法不可行时才应选用关联对象,因为这种做法通常会引入难于查找的bug。

11 理解objc_msgSend的作用

总结:边界情况(特殊情况),objc_msgSend_stret返回结构体,objc_msgSend_fpret返回浮点数,objc_msgSendSuper。尾调用优化技术:如果某函数的最后一项操作是调用另外一个函数,而不会将其返回值另作他用时,才能执行“尾调用优化”。只保留内层函数的调用记录,如果所有函数都是尾调用,那么完全可以做到每次执行时,调用记录只有一项,这将大大节省内存。消息由接收者,选择子及参数构成。给某对象发送消息,也就相当于在该对象上调用方法。发给某对象的全部消息都要有动态消息派发系统来处理,该系统会查出对应的方法,并执行其代码。

12 理解消息转发机制

总结:消息转发分为两大阶段,一动态方法解析:先征询接收者,所述的类,看能否添加动态方法;二完整的消息转发机制:先看有没有其他对象能处理这条消息。若没有则启动完整的消息转发机制,运行期系统会把与消息有关的全部细节都封装到NSInvocation对象中。

+ (BOOL)resolveInstanceMethod:(SEL)selector;(尚未实现的是实例方法调用这个方法)

+(BOOL)resolveClassMethod:(SEL)selector;(尚未实现的是类方法)

用+ (BOOL)resolveInstanceMethod:(SEL)selector实现@dynamic属性

return YES;或者是return [super resovleInstanceMethod:selector];动态添加方法

- (id)forwardingTargetForSelector:(SEL)selector;可以模拟出多重继承的某些特性,但是无法操作由这一步所转发的消息。

- (void)forwardInvocation:(NSInvocation*)invocation;一是改变调用目标,但是与上一步效果等效。二是出发消息前,先以某种方式改变消息内容,追加另外一个参数,或是改换选择子。

CALayer是兼容与键值编码的容器类,就是说,能够向里面随意添加属性,然后以键值对的形式来访问。属性值的存储工作由基类直接负责,我们只需在CALayer的子类中定义新属性即可。

若对象无法响应某个选择子,则进入消息转发流程。通过运行期的动态方法解析功能,我们可以在需要用到某个方法时再将其加入类中。对象可以把其无法解读的某些选择子转交给其他对象来处理。经过上述两步之后,如果还是没有办法处理选择子,那就启动完整的消息转发机制。

13 用方法调配技术调试黑盒方法

总结:交换方法用void method_exchangeImplementations(Method m1,Method m2);方法实现用Method class_getInstanceMethod(class aClass,SEL aSelector)。通过此方案,可以为那些完全不知道其具体实现的黑盒方法增加日志记录功能,有助于程序调试。但若滥用,会使代码变得不易读懂且难于维护。

14 理解类对象的用意

总结:在程序中不要直接比较对象所属的类,明智的做法是调用类型信息查询方法,isKindOfClass和isMemberOfClass,也可用比较类对象是否等同的办法来做。若是如此,就要使用==操作符,不要用isEqual,因为类对象是单利,在应用程序范围内,每个类的Class仅有一个实例。每个类仅有一个类对象,所以可以使用==,而每个类对象仅有一个与之相关的元类。尽量使用类型信息查询方法来确定对象类型,而不要直接比较对象,因为某些对象可能实现了消息转发功能,如果用直接比较的方法class返回的对象和查出来的类对象是不同的,class方法所返回的类表示发起代理的对象,而非接受代理的对象。