用OC等面向对象语言编程时,“对象”(object)就是“基本构造单元”(building block),开发者可以通过对象来存储并传递数据。在对象之间传递数据并执行任务的过程就叫做“消息传递”(Messaing)。
当应用程序运行起来以后,为其提供相关支持的代码叫做“Objective-C运行期环境”(Objevtive-C runtime),它提供了一些使得对象之间能够传递消息的重要函数,并且包含创建类实例所用的全部逻辑。在理解了运行期环境各个部分协同工作的原理之后,你的开发水平会进一步提升。
第6条:理解“属性”这一概念
“属性”(property)是OC的一项特性,用于封装对象中的数据。OC对象通常会把其所需要的数据保存为各种实例变量。属性还有一些访问方法setter和getter。这一概念已经定型,开发者可以令编译器自动编写与属性相关的访问方法。
在描述个人信息的类中,也许会存放人名、生日、地址等内容。可以在类接口的public区段中声明一些实例变量:
@interface EOCPerson : NSObject{ @public NSString* _firstName; NSString* _lastName; @private NSString* _someInternalData; } @end;
原来用过Java和C++的人比较熟悉这种写法,可以定义变量的作用域。OC很少这么做。该写法的变量内存布局在编译期已经固定了。如图所示:
详细见《深度探索C++对象模型》http://blog.csdn.net/hherima/article/details/8888539
如果EOCPerson类中添加了一个属性NSString* _dateOfBirth,那么代码需要重新编译,否则就会出错。例如,某个代码库中的代码使用了一份旧的类定义。如果和其相链接的代码使用了新的类定义,那么运行时就会出现不兼容现象。各种编程语言都有应对此问题的方法。Objetive-C的做法是,把实例变量当做一种存储偏移量所有的“特殊变量”(special variable),交由“类对象”保管。偏移量会在运行期查找,如果类的定义变了,那么存储的偏移量也就变了,这样的话,无论何时访问实例变量,总能使用正确的偏移量。甚至可以在运行期向类中新增实例变量,这就是稳固的“应用程序二进制接口”(Application
Binary Interface,ABI)
@property语法,能够访问封装在对象里的数据。因此,也可以把属性当做一种简称,其意思是说:编译器会自动生成一套访问方法,用以访问给定类型中具有给定名称的变量。例如:
@interface EOCPerson : NSObject @proterty NSString* firstName; @proterty NSString* lastName; @end
对于类的使用者来说,上述代码写出来的类与下面这种写法等效:
@interface EOCPerson : NSObject -(NSString*)firstName; -(void)setFirstName:(NSString*)firstName; -(NSString*)lastName; -(void)setLastName:(NSString*)lastName; @end
访问属性使用点语法,属性另外一个好处是可以使用自动合成@synthesize,另外@dynamic关键字可以告诉编译器不要自动生成属性访问方法,这里不再累赘。
属性特质
属性有四种特质:原子性、读/写权限、内存管理语义和方法名:
1. 原子性 默认情况下是atomic特质(尽管没有atomic这个特质),nonatmic告诉编译器不使用同步锁。
2. 读/写权限 具备read/write(读写)特质的属性拥有访问方法。若属性由@synthesize实现,则编译器会自动生成这两个方法。具备readonly(只读)特质的属性仅拥有getter方法。
3. 内存管理语义
● assign 只会针对纯类型简单赋值操作
● strong 拥有关系,为这种属性设置新值的时候,保留新值,释放旧值。
● weak 同assign,但是对象销毁的时候,属性值也会清空
● unsafe_unretained 同assign,它适用于“对象类型”这也是跟assign的唯一区别
● copy 跟strong的特质类似。然而设置setter不会保留新值,而是将其“拷贝”(copy)。当属性类型是NSMutableString*时,需要copy特质,目的是为了防止自己的属性被无意修改。参考《Effective Objective-C 2.0》—(三)—、接口与API设计、深拷贝、浅拷贝 》最后部分http://blog.csdn.net/hherima/article/details/38403277
4. 方法名
● getter=<name>指定获取方法名
● setter=<name>指定“设置方法”名
本节要点
● 使用@property 语法来定义对象中封装的数据
● 通过“特质”来指定存储数据所需的正确语义。
● 在设置属性对应的实例变量时,一定要遵从该属性声明的语义
● 开发iOS程序时候应该使用nonatomic特质,因为atomic会严重印象性能。
第7条:在对象内部尽量直接访问实例变量
在对象之外访问实例变量时,总是应该通过属性来做,那么在对象内部?笔者强烈建议大家在读取实例变量的时候采用直接访问的形式,而在设置实例变量的时候通过属性来做。之所以要通过“设置方法”来写入实例变量,首要原因在于,这样能够确保相关属性的“内存管理语义”得以贯彻。
第8条:理解“对象等同性”这一概念
NSObject协议中有两个判断等同性的关键方法:
-(BOOL) isEqual:(id)object; -(NSUInteger)hash;
NSObject类对这两个方法的默认实现是:当且仅当“指针值”(pointer value,可理解为内存地址)完全相等时,这两个对象才相等。如果"isEqual:"方法判定两个对象相等,那么其hash方法必须返回同一个值。但是,如果两个对象的hash方法返回同一个值,那么“isEqual:”方法未必会认为两者相等。
先看看"isEqual:"接口,如果一个类中有A,B,C三个字段,那么isEqual就要对三个地段逐个对比,然后都相同的时候就返回ture。
再看看hash方法,一般的做法是,将A,B,C三个字段分别hash,最后再异或一下。
-(NSUInteger)hash{ NSUInteger firstNameHash = [_firstName hash]; NSUInteger lastNameHash = [_lastName hash]; NSUInteger ageHash = _age; return firstNameHash ^ lastNameHash ^ ageHash }
这种做法既能保持高效率,又能使生成的哈希码至少位于一定范围之内,而不会过于频繁的重复。
特定类型所具有的等同性判定方法
除了刚才提到的NSString之外,NSArray与NSDictionary类也有判定方法,前者是“isEqualToArray”后者是“isEqualToDictionary”。
除了刚才
等同性判定的执行深度
创建等同性判断方法时,需要决定是根据整个对象来判断,还是仅根据其中几个字段来判断。NSArray的检测方式为先看两个数组所含有对象个数是否相等,若相同,则在每个对应位置的两个对象身上调用其“isEqual”方法,如果对应位置上的对象均相等,那么这两个数组就相等,这叫做“深度等同判定”
容器中可变类的等同性
直接举例NSSet就好。
第一步:在NSSet中添加一个可变数组A[@1,@2];那么set中就有了一个对象,{[@1,@2]}
第二步:再向NSSet中添加一个可变数组B[@1,@2],由于数组A和B是一样的,所以set中仍是一个对象,这也负责set的特性。
第三步:向NSSet中添加一个可变数组C[@1],那么set中就有了两个对象,{[@1],[@1,@2]}.
第四步:我们改变数组C的值为[@1,@2],那么set居然包含了两个相同的对象,{[@1,@2],[@1,@2]}.这不符合set的特性。
第五步:复制一份set。新的set的内容又变成{[@1,@2]}了。看上去set好像是由一个空set开始,通过逐个向其中添加新对象而创建出来的。
这是因为像set这样的collect,把某个对象放入collect之后,就不应该改变其哈希码了。
本节要点:
● 若想检测对象的等同性,请提供“isEqual:”与hash方法。
● 相同的对象必须具有相同的哈希码,但是两个哈希码相同的对象未必相同。
● 不要盲目的逐个检测每个属性,而是应该依照具体需求来定制检测方案
● 编写hash方法时,应该使用计算速度快而且哈希码碰撞几率低的算法。
第9条:以“类族模式”隐藏实现细节
“类族”(class cluster)模式是一种很有用的模式(pattern),可以隐藏“抽象基类”(abstract base class)背后的实现细节。Objective-C的系统框架中普遍使用此模式。比如,iOS的用户界面框架UIKit中就有一个名为UIButton类。
第10条:在既有类中使用关联对象存放自定义数据
直接列举开发中常见的例子吧:一个UIAlertView的使用,通常的做法是这样的:
-(void) askUserAQuestion{ UIAlterView *alert = [[UIAlterView alloc] initWithTitle:@"Question" message:@"what do you want to do?" delegate:self cancelButtonTitle:@"cancel" otherButtonTitle:@"cancel",nil]; [alert show]; } //UIAlertViewDelegate protocol method -(void)alertView:(UIAlertView*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{ if(buttonIndex == 0){ [self doCancel]; } else { [self doContinue]; } }
alertView的创建和处理,不在一个地方。开发过程中,经常来回滑动代码查看。那么“关联对象”就起到作用了,将操作跟UIAlertView关联起来。例如:
static void* EOCMyAlertKey = "EOCMyAlertViewKey"; -(void) askUserAQuestion{ UIAlterView *alert = [[UIAlterView alloc] initWithTitle:@"Question" message:@"what do you want to do?" delegate:self cancelButtonTitle:@"cancel" otherButtonTitle:@"cancel",nil]; void (^block)(NSInteger) = ^(NSInteger buttonIndex){ if(buttonIndex == 0){ [self doCancel]; } else { [self doContinue]; } }; objc_setAssociatedObject(alert,EOCMyAlertViewKey,block,OBJC_ASSOCIATION_COPY); [alert show]; } //UIAlertViewDelegate protocol method -(void)alertView:(UIAlertView*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{ if(buttonIndex == 0){ [self doCancel]; } else { [self doContinue]; } } -(void)alertView:(UIAlertView*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{ void (^block)(NSInteger) = objc_getAssociatedObject(alertView,EOCMyAlertViewkey); block(buttonIndex); }
介绍一下改进代码中的技术
● void objc_setAssociatedObject(id object, void*key ,id value,objc_AssociatedPolicy policy)
此方法以给定的键和策略为某对象设置关联对象值
● id objc_getAssociatedObject(id object,void* key)
此方法根据给定的键从某对象中获取对应的关联对象值
● void objc_removeAssociatedObjects(id object);
下图是对象关联类型
但是,这个方法应该慎用,在使用block的时候小心循环引用(参考bloc的介绍)。所以一般的做法还是:从中继承子类,把block保存为子类中的属性。
《Effective Objective-C 2.0》—(第6-10条)—对象、属性、equalToString、关联对象