【Effective Objective-C 2.0读书笔记】第二章:对象、消息、运行期
来源:互联网 发布:绿化预算软件 编辑:程序博客网 时间:2024/05/16 11:52
在Objective-C等面向对象语言中,“对象”是基本构造单元,开发者可以通过对象来存储并传递数据。在对象之间传递数据并执行任务的过程即为“消息传递”。
当应用程序运行起来之后,为其提供相关支持的代码叫做“Objective-C运行期环境”(Objective-C runtime),它提供了一些使得对象之间能够传递消息的重要函数,并且包含创建类实例所用的全部逻辑。
第6条:理解”属性”这一概念
属性可以拥有的特质分为四类:
原子性
atomic
:原子属性,默认就是atomic。需要消耗大量的资源。
nonatomic
:非原子属性。适合内存小的移动设备。
默认情况下,由编译器所合成(synthesize
)的方法会通过锁定机制来确保它是原子的(atomic
)。如果属性具有nonatomic
特质,则不使用同步锁。
需要注意的是,开发iOS程序时一般都会使用nonatomic
属性,这是因为在iOS中使用同步锁的开销较大,这会带来性能问题。但是在Mac OS X中,使用atomic
属性通常都不会有性能瓶颈。
一般情况下,并不要求属性必须是原子的,因为这并不能保证“线程安全”,若要实现线程安全,需要更为深层的锁定机制才行。例如一个线程要连续多次读取某属性的过程中,另一个线程对该属性值进行了修改,那么即便将属性声明为atomic
,还是会读到不同的属性值。
读/写权限
readwrite
:表明属性具有读取和设置方法。
readonly
:表明属性只拥有读取方法。
若属性由@synthesize
实现,则编译器才会自动合成与其读写权限相关的方法。
内存管理语义
内存管理语义仅会影响属性的“设置方法”。编译器在合成存取方法时,要根据此特质来决定所生成的代码。如果自己来编写存取方法,那么就必须与相关属性所声明的特质相符。
assign
:只执行针对“纯量类型”(scalar type,例如CGFloat或NSInteger等)的简单赋值操作。strong
:表明该属性定义了一种拥有关系。为该属性设置新值时,会先保留新值,并释放旧值,最后再设置新值。weak
:表明该属性定义了一种非拥有关系。为该属性设置新值时,既不保留新值,也不释放旧值。此特质跟assign
类似,然而在属性所指对象被销毁时,该属性也会清空(nil out)。unsafe-unretained
:此特质的语义跟assign
相同,但它适用于对象类型。表明该属性定义了一种非拥有关系,在属性所指对象被销毁时,该属性不会自动清空,这点跟weak
不同。copy
:此特质所表达的所属关系跟strong
类似。然而设置方法并不保留新值,而是将其拷贝。只要实现属性所用的对象是可变的(mutable),就应该在设置新属性值时拷贝一份。当属性类型为NSString*
时,经常用此特质来保护其封装性,因为传递给设置方法的新值有可能是NSMutableString
类型的实例。
方法名
可通过如下特质来指定存取方法的方法名。
getter=<name>
:指定“获取方法”的方法名。例如:
@property (nonatomic, getter=isOn) BOOL on;
setter=<name>
:指定“设置方法”的方法名,这种用法不太常见。
第7条:在对象内部尽量直接访问实例变量
笔者强烈建议除了几种特殊情况之外,在读取实例变量时采用直接访问的形式,而在设置实例变量的时候通过属性来做。
一种特殊情况是在初始化方法中,一般设置实例变量时都是采用直接访问来设置的形式。
另一种特殊情况是惰性初始化,在这种情况下必须通过“获取方法”来访问属性,否则实例变量就永远不会初始化。
要点:
在初始化方法及
dealloc
方法中,总是应该直接通过实例变量来读写数据。
第8条:理解”对象等同性”(Object Equality)这一概念
比较对象的等同性是一个非常有用的功能。不过按照==
操作符比较的是比较两个对象的指针地址,而不是其所指的对象。应该使用NSObject
协议中的isEqual
方法来判断两个对象的等同性。NSObject
类对isEqual
方法的默认实现是当且仅当两个对象的指针值相等时,才判定这两个对象相等,这时hash
方法返回的值也必须相等。
例如有一EOCPerson
类,包含若干字段,isEqual
方法可实现如下:
- (BOOL) isEqual:(id)object{ if([self class] == [object class]){ return [self isEqualToPerson: (EOCPerson *)object]; } else { return [super isEqual: object]; } return NO;}- (BOOL) isEqualToPerson:(EOCPerson *)otherPerson{ if(self == otherPerson) return YES; if([_firstName isEqualToString: otherPerson.firstName] && [_lastName isEqualToString: otherPerson.lastName] && _age != otherPerson.age]) return YES; return NO;}
计算hash值的方法可实现如下,这样既能保持高效率,又能使生成的hash码至少落在一定范围之内,不会频繁重复:
- (NSUInteger)hash{ NSUInteger firstNameHash = [_firstName hash]; NSUInteger lastNameHash = [_lastName hash]; NSUInteger ageHash = _age; return firstNameHash ^ lastNameHash ^ ageHash;}
要点:
若要检查对象的等同性,请提供
isEqual
和hash
方法。相同的对象必须具有相同的hash码,但拥有相同hash码的对象却不一定相同。
编写hash方法时,应该使用计算速度快而且哈希码碰撞几率低的算法。
第9条:以”类簇模式”隐藏实现细节
在Cocoa中,许多类实际上是以类簇的方式实现的,即它们是一群隐藏在通用接口之下的与实现相关的类。例如创建NSString对象时,实际上获得的可能是NSLiteralString、NSCFString、NSSimpleCString、NSBallOfString或者其他未写入文档的与实现相关的对象。
类簇的抽象基类的实现示例:
typedef NS_ENUM(NSUInteger, EOCEmployeeType) { EOCEmployeeTypeDeveloper, EOCEmployeeTypeDesigner, EOCEmployeeTypeFinance,};@interface EOCEmployee : NSObject@property (copy) NSString *name;@property NSUInteger salary;// Helper for creating Employee objects+ (EOCEmployee*)employeeWithType:(EOCEmployeeType)type;// Make Employees do their respective day's work- (void)doADaysWork;@end@implementation EOCEmployee+ (EOCEmployee*)employeeWithType:(EOCEmployeeType)type { switch (type) { case EOCEmployeeTypeDeveloper: return [EOCEmployeeDeveloper new]; break; case EOCEmployeeTypeDesigner: return [EOCEmployeeDesigner new]; break; case EOCEmployeeTypeFinance: return [EOCEmployeeFinance new]; break; }}- (void)doADaysWork {// Subclasses implement this.}@end
类簇的实体子类的实现示例:
@interface EOCEmployeeDeveloper : EOCEmployee@end@implementation EOCEmployeeDeveloper- (void)doADaysWork { [self writeCode];}@end
判断某对象是否位于类簇中,不要直接检测两个“类对象”(class)是否相等,而应该使用类型信息查询方法:
id maybeAnArray = /* ... */;if ([maybeAnArray isKindOfClass:[NSArray class]]) { // Will be hit}
要点:
类簇模式可以把实现细节隐藏在一套简单的公共接口后面。
系统框架中经常使用类簇。
从类簇的公共抽象基类中继承子类时要当心,若有开发文档,则应首先阅读。
第10条:在既有类中使用关联对象存放自定义数据
管理关联对象的方法有:
// Sets up an association of object to value with the given key and policy.void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicypolicy)// Retrieves the value for the association on object with the given key.id objc_getAssociatedObject(id object, void *key)// Removes all associations against object.void objc_removeAssociatedObjects(id object)
要点:
可以通过“关联对象”(associated objects)机制将两个对象连起来。
定义关联对象时,可指定内存管理语义,用以模仿定义对象属性时所采用的“拥有关系”与“非拥有关系”。
只有在其他方法不可行时才采用关联对象,因为这种做法通常会引入难以查找的bug。
第11条:理解objc_msgSend的作用
C语言中有静态绑定和动态绑定两种函数调用方式。Objective-C作为C语言的超集,向对象发送消息时使用动态绑定机制来决定需要调用的方法。在底层,所有方法都是普通的C语言函数,然而在对象收到消息后究竟调用哪个方法则完全于运行期决定,甚至可以在程序运行时改变,这些特性使得Objective-C成为一门真正的动态语言。
objc_msgSend函数会根据接收者与选择子的类型来调用适当的方法。为了完成此操作,该函数需要在接收者所属的类中搜寻其“方法列表”,如果找到与选择子名称相符的方法,就跳转到其实现代码;否则即沿着继承体系继续向上查找;如果仍未找到,就执行“消息转发”(message forwarding)操作。
objc_msgSend调用方法有个优化操作。它会将匹配结果缓存在“快速映射表”(fast map)里。每个类都有这样一块缓存,若是稍后还发送相同的消息,就会加快执行效率。
前面讲的这部分内容只描述了部分消息的调用过程,其他“边界情况”(edge case)则需要交由Objective-C运行环境中的另外一些函数来处理:
objc_msgSend_stret
objc_msgSend_fpret
objc_msgSendSuper
要点:
消息由接受者、选择子和参数构成。给某对象“发送消息”(invoke a message),相当于在该对象上“调用方法”(call a method)。
发送给某对象的全部消息都要经过“动态消息派发机制”(dynamic message dispatch system)来处理,该系统会查出对应的方法,并执行其代码。
第12条:理解消息转发机制
第11条讲述了对象的消息传递机制;第12条承接第11条,在对象收到无法解读的消息之后,就会启发“消息转发”(message forwording)机制,程序员可经由此过程告诉对象应该如何处理未知消息。
消息转发的顺序如下:
动态方法解析: 向当前对象的所属类发送
resolveInstanceMethod:
(针对实例方法)或resolveClassMethod
(针对类方法)消息,检查是否动态向该类添加了方法。使用此方案的前提是:相关的实现代码已经写好,只等着运行时直接插在类中。此方案常用来实现@dynamic
属性。快速消息转发: 检查该对象是否实现了
forwardingTargetForSelector:
实例方法,若实现了则调用这个方法,将对象无法解读的某些选择子转交给其他对象来处理。如果对象实现了此方法,并返回一个非nil的结果,则这个对象会作为消息的新接收者。当然返回值不能是self自身,否则会无限循环。如果我们没有指定相应的对象来处理选择子,则应调用父类的实现来返回结果。使用这个方法通常是在对象内部,可能还有一系列其它对象能处理该消息,我们便可借这些对象来处理消息并返回,这样在对象外部看来,还是由该对象亲自处理了这一消息。标准消息转发: 经过上述两步之后,如果还是无法处理选择子,则启动完整的消息转发机制。我们需要重写
methodSignatureForSelector:
和forwardInvocation:
实例方法。runtime发送methodSignatureForSelector:
消息获取选择子对应的方法签名,即参数与返回值的类型信息。runtime则根据方法签名创建描述该消息的NSInvocation
,以创建的NSInvocation
对象作为参数,向当前对象发送forwardInvocation:
消息。forwardInvocation:
方法定位能够响应封装在此NSInvocation
中的消息的对象。此NSInvocation
对象将会保留调用结果,运行时系统会提取这一结果并将其发送到消息的原始发送者。在这个方法中我们可以实现一些更复杂的功能,可对消息内容进行修改,比如追回一个参数等,然后再去触发消息。另外,若发现某个消息不应由本类处理,则应调用父类的同名方法,以便继承体系中的每个类都有机会处理此调用请求。NSObject的forwardInvocation:
方法实现只是简单调用了doesNotRecognizeSelector:
方法,它不会转发任何消息,只抛出异常导致程序退出。1
第13条:用”方法调配”(method swizzling)技术调试”黑盒方法”
方法调配技术可以在运行期改变与给定的选择子名称相对应的方法。如果善用该特性,则可发挥巨大优势,因为我们不需源代码,也不需通过继承子类来覆写方法就能改变这个类本身的功能。新功能将在本类的所有实例中生效。2
要点:
在运行期,可以向类中新增或替换选择子所对应的方法实现。
使用另一份实现来替换原有方法的实现,称为”方法调配”(method swizzling),开发者常用此技术向原有实现中添加新功能。一般来说,只有调试程序的时候才需要在运行期修改方法实现,这种做法不宜滥用。
第14条:理解”类对象”的用意
“在运行期检视对象类型”这一操作也叫做“类型信息查询”(introspection,“内省”),这个强大有用的特性内置于Foundation框架的NSObject
协议里,凡是由公共根类(common root class,即NSObject
与NSProxy
)继承而来的对象都遵从此协议。在程序中,不要直接比较对象所属的类,明智的做法是调用“类型信息查询方法”。
类型信息查询方法包括isMemberOfClass:
(判断对象是否为某个特定类的实例),isKindOfClass:
(判断对象是否为某类或其派生类的实例)。像这样的类型信息查询方法使用isa
指针获取对象所属的类,然后通过super_class
指针在继承体系里游走。
另外一种可精确判断出对象是否为某类实例的办法是:
id object = /* ... */;if([object class] == [EOCSomeClass class]){ // 'object' is an instance of EOCSomeClass}
即使这样,应尽量使用类型信息查询方法,而不应直接比较两个类对象是否等同,因为前者可以正确处理那些使用了消息转发机制的对象。比如,某对象可能会把它收到的所有选择子都转发给另外一个对象。这样的对象叫做代理,此种对象均以NSProxy
为根类。
要点:
每个实例都有一个指向
Class
对象的指针,用以表明其类型,而这些Class
对象则构成了类的继承体系。如果对象类型无法在编译期确定,那么就应该使用类型信息查询方法来探知。
尽量使用类型信息查询方法来确定对象类型,而不要直接比较类对象,因为某些对象可能实现了消息转发功能。
- http://southpeak.github.io/blog/2014/11/03/objective-c-runtime-yun-xing-shi-zhi-san-:fang-fa-yu-xiao-xi-zhuan-fa/ “Objective-C Runtime 运行时之三:方法与消息” ↩
- http://southpeak.github.io/blog/2014/11/06/objective-c-runtime-yun-xing-shi-zhi-si-:method-swizzling/ “Objective-C Runtime 运行时之四:Method Swizzling” ↩
- 【Effective Objective-C 2.0读书笔记】第二章:对象、消息、运行期
- effective objective-c 2.0 笔记 第二章 :对象,消息,运行期
- Effective Objective-- 对象,消息,运行期
- 《Effective Objective-C 2.0》读书笔记---第二章
- 第二章:对象、消息、运行期
- 第二章 对象、消息、运行期
- Effective Object C 2.0——对象、消息和运行期
- 《Effective Objective-C 2.0》读书笔记---第三章
- 《Effective Objective-C 2.0》读书笔记---第四章
- 《Effective Objective-C 2.0》读书笔记---第五章
- 《Effective Objective-C 2.0》读书笔记---第七章
- Effective Objective-C 2.0 读书笔记
- 《Effective Objective-C 2.0》读书笔记
- Effective Objective-C 第二章
- Effective Objective-C读书笔记
- 【Effective Objective-C 2.0读书笔记】第七章:系统框架
- 【Effective Objective-C 2.0读书笔记】第五章:内存管理
- 【Effective Objective-C 2.0读书笔记】第四章:协议和分类
- Unity3d中UI开发的MVC模式
- 2012年5月SAT香港真题解析
- javascript的函数和对象
- Ubuntu系统访问Windows共享文件夹的方法
- AndroidStudio 添加svn插件
- 【Effective Objective-C 2.0读书笔记】第二章:对象、消息、运行期
- 计算机网络基础
- 样条曲线
- 理解Python的With语句
- linux系统下apache服务的启动、停止、重启命令
- Android 开源项目android-open-project解析之(二) GridView,ImageView,ProgressBar,TextView
- Memcache与Redis比较
- 13:在O(1)时间删除单链表节点
- 造随机数的c++代码