在Java或C++中,对象布局在编译器(compile time)已经固定了,对象的关于成员变量的代码被编译器替换为“偏移量(offset)”,这个偏移量是“硬编码(hardcode)”,表示这个变量距离存放对象的内存区域的起始地址有多远。这种情况下,如果修改类定义之后就必须重新编译,否则就会出错。
OC在这方面的优化是,把实例变量当做是一种存储偏移量所用的“特殊变量(special variable)”,交由类对象(class object)保管。偏移量会在运行期查找,如果类的定义变了,那么存储的偏移量也就变了,这样的情况下,无论何时访问实例变量总能够使用到正确的偏移量。还可以在运行期向类中添加新的实例变量。
使用@property语法定义属性 编译器会自动写出一套存取方法,用以访问给定类型中具有给定名称的变量。在此之外,编译器还会向类中添加适当类型的实例变量,并且在属性前添加下划线,以此作为实例变量的名字。
编译器会把“.”点语法转化为对存取方法的调用。
属性的修饰关键字:
1.原子性: nonatomic/atomic
由编译器所合成的方法会通过锁定机制确保其原子性atomic,如果自己定义存取方法,注意些和属性的原子性相符的代码。
但是我们一般把属性都声明为nonnatomic,因为在iOS开发中,使用同步锁的开销比较大,会带来性能的问题,如果要实现“线程安全”的操作,还需要采取更为深层的锁定机制才行。
2.读写权限:readwrite(读写)/readonly(只读)
readwrite的属性拥有getter、setter方法;readonly的属性只有getter方法;
3.内存管理:assign / strong / weak / unsafe_unretained / copy
assign:只会对基本纯量类型CGFloat,NSInteger等进行简单的赋值;
strong:定义了一种拥有关系,设置新值时,会保留新值,释放旧值,然后将新值设置上去;
weak:定义了一种非拥有关系,设置新值时,不保留新值,也不保留旧值,在属性所指的对象释放时,属性值会清空;
unsafe_unretained:相当于是对象类型的assign,在属性所指的对象释放时,属性值不会清空;
copy:定义了一种拥有关系,但是设置新值时,不会保留新值,而是将其拷贝。
4.方法名:getter=<name> setter=<name>
getter=<name> 指定设置方法的方法名
setter=<name> 指定获取方法的方法名
在对象内部读取实例变量的时候,用直接访问的形式;设置实例变量的时候通过属性来做。
直接访问实例变量,编译器会直接访问保存对象实例变量的那块内存;并且不会调用其设置方法(setter),这样就绕过了属性定义的“内存管理语句”;也不会触发“键值观察KVO”。
但是在初始化方法中应该直接访问实例变量;在懒加载中应该使用属性,来通过setter方法访问实例变量。
对象的等同性:
当比较两个对象是否相等的时候,如果我们使用"=="来比较的话,比较的将是两个对象的指针,如果这两个指针指向同一个地址才相等,否则是不相等的。这显然和我们所想的不符合,我们就可以通过重写NSObject的"isEqual:",来书写我们自己的判断逻辑。比如说NSString类实现了自己的对比方法"isEqualToString"。
NSObject类中有两个可以判定两个对象是否具有等同的关键方法:
-(BOOL)isEqual:(id)Object;
-(NSUInteger)hash;
如果两个对象通过isEqual判定相等,那么两个对象返回的hash值必须是同一个值;
相反,如果两个对象的hash返回值是同一个值,isEqual判断可以不相等。
当我们写对象的同等性判断时,应该重写isEqual方法,如果被检测参数和接受该消息的对象都属于同一个类的话,就调用自己编写的判定方法,否则就交由超类来判断。
举个例子:
//假设类名为 myclass 有两个NSString属性 name age
- (BOOL)isEqualToMyclass:(myclass)cls{
if(self == cls) return YES;
if(![_name isEqualToString:cls.name]){
return NO;
}
if(![_age isEqualToString:cls.age]){
return NO;
}
return YES;
}
- (BOOL)isEqual:(id)object{
if([self class] == [object class]){
return [self isEqualToMyclass:(mycalss*)object];
}else
return [super isEqual:object];
}
//推荐计算hash的方法
//如果属性过多 不要盲目的去检测每一个属性 要根据实际情况来区分
//编写hash方法时,应该使用计算速度快且碰撞几率低的算法
- (NSUInteger)hash{
NSUInteger nameHash = [_name hash];
NSUInteger ageHash= [_age hash];
return nameHash^ageHash;
}
有一点要注意的是,如果放对象放入数组、字典、集合中后,又改变了该对象,可能会出现问题。