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

2016-10-31  本文已影响0人  LucXion

属性是OC的一项特征,用于封装对象中的数据,OC对象通常会把其所需要的的数据保存为各种实例变量。

对象的布局:
Java和C++中,对象的布局在编译器就已经固定了, 访问其中某一项变量,编译器就会把其替换为“偏移量”,这个偏移量是“硬编码”,表示该变量距离存放对象的内存的起始地址有多远。这样的缺点在于:代码使用了编译器计算出来的偏移量,那么在修改类定义后必须重新编译,如果代码库中使用的是一份旧的类定义,如果何其相连的代码使用了一份新的类定义,那么就会出现不兼容的现象(incompatibility)。

OC中的做法是,将实例变量当成存储偏移量的特殊变量

@property NSString * name;
@impretment
@synthesiize name = _nickName; (将生成的name 重命名为_nickName)

@dynamic name(告诉编译器不要生成存取方法,访问属性的代码时编译器也不会报错,例如使用到CoreData框架)

assign:“设置方法”只会针对“纯量类型”(CGFloat等)做简单的赋值操作
strong:此特性表明了一种“拥有关系”,为这种属性设置新值时,保存新值->释放旧值->将新值设置上去
weak:“非拥有关系”,既不保存新值,也不释放旧值,此特性和assign类似,但在旧值被摧毁的时候,属性值也会被清空
unsafe_unretained: 语义与assign相似,但是适用于对象,在目标对象被摧毁时,属性值不会被清空
copy:语义与strong相似,但不保存新值,而是将其拷贝,通常修饰NSString用以保存它的封装性

strong和copy的区别在于是否希望源值改变的时候你的属性是否也跟着改变(在这一层考虑用copy比较保险),但是oc是弱语言,在修饰可变数组或者可变的变量时,不用copy可能会导致可变指针指向不可变变量,导致崩溃,所以修饰可变时用strong

方法名:getter BOOL类型重定义getter方法 getter = isXXX

Atomic :确保原子性(严重影响性能),同步锁,但不能保证线程安全,例如在一个线程连续多次读取某属性过程中,有别的线程同时修改属性的值,还是可能会读取错误的值,但是在MacOSX的开发环境下,通常不会有性能瓶颈。

直接访问实例变量:
1.速度比较快,编译器所生成的代码会直接访问保存变量实例变量的那块内存
2.直接访问实例变量会绕过“内存管理语义”
3.直接访问实例变量会绕过“KVO”

1.在对象内部读取数据时,应该直接通过实例变量来读,而写入数据时,则应该通过属性来写
2.在初始化和dealloc方法中,应该总是直接通过实例变量来读写数据
3.使用懒加载,应该通过属性来读取数据

对象等同性
“==”只能判断指针是否相同
NSObjective 判断等同性的关键方法为

-(BOOL)isEqual:(id)object
-(NSUInteger)hash
类似isEqualToString方法的性能优于isEqual(少类型判断环节)

检查对象的等同性,需要提供isEqual和hash方法
相同的对象具有相同的hash码,相同的hash码并不一定就是相同的对象
不要逐个盲目的检测每条属性,而是应该根据具体的需求制定检测方案(重写isEqual和hash方法)

isEqual的原理:
1.先判断指针,再判断类型,接着逐个属性判断(有一条不符既return NO)
2.如果类型不匹配,那么将不会走
3.isEqualToString这类方法,而是走基类的isEqual

注意,当重写-(BOOL)isEqual:(id)object时,重写-(NSUInteger)hash的注意点
-(NSUInteger)hash{   
    //不需要参与CollectionView    
    // return 1024; 

    //需要参与CollectionView
    NSUInteger name = [_name hash];   
    NSUInteger nickName = [_nickName hash];    
    return name ^ nickName;
}

编写hash方法时,应该使用计算速度快切hash码碰撞几率低的方法

1.类族模式可以把实现细节隐藏在一套简单的公共接口后面(子类应该自定义自己的存储方式)

基类不应该有init接口, 或者在doADaysWork中抛出异常(暴力,不推荐)

关联对象,相当于临时的动态添加属性,非不得已不用

import <objc/runtime.h>
setter : objc_setAssociatedObject(luc, (__bridge const void *)(objcKey), block, OBJC_ASSOCIATION_COPY_NONATOMIC);
getter : void(^block)(NSInteger) = objc_getAssociatedObject(self.luc, (__bridge const void *)(objcKey));

每一条消息发送都可以看做是一个这样的C函数

objc_msgSend(id self,SEL cmd,…);

当消息遇到“边界情况”时,则需要交给object-c运行环境中的另外函数处理:
返回值为结构体:objc_msgSend_stret
返回值为浮点数:objc_msgSend_fpret
给超类发送消息:objc_msgSendSuper

实现的步骤: 在消息者所属的类中搜寻其”方法列表“,如果能找到与选择子名称相符的方法,就跳至其实现代码,若是找不到,就沿着继承体系继续向上查找,等找到合适的方法再跳转,如果一直找不到,那就执行消息转发操作。发送成功,那么objec_msgSend会将匹配结果缓存到”快速映射表中fastmap“,每个类都有一块这样的缓存。

消息转发分为两个大阶段P58

+(BOOL)resolveInstanceMethod:(SEL)sel(动态添加实例方法)
+(BOOL)resolveClassMethod:(SEL)sel
-(id)forwardingTargetForSelector:(SEL)aSelector(我们无法经由这一步操作转发的消息)
-(void)forwardInvocation:(NSInvocation *)anInvocation
64A0FBDB-3A46-42AE-819E-972322797EC2.png

方法交换(多用于调试):

Method peopleRun = class_getInstanceMethod([People class], @selector(run));    Method peopleEat = class_getInstanceMethod([People class], @selector(eat));    method_exchangeImplementations(peopleRun, peopleEat);

描述OC对象所用的数据结构定义在运行期程序库的头文件里,id类型本身也定义在这里

typedef struct objc_object {
    Class isa;
}*id;

由此可见每个对象结构体的收个成员是Class类型的变量,该变量定义了对象所属的类,通常称为is a指针

假设有一个类SomeClass的子类从NSObject 中继承而来,其继承体系如下:

8B1C1CA5-5E0A-44F2-816D-3BED6BF35C1B.png

总结:
每个实例都有一个指向Class对象的指针,用以表明其类型,这些Class对象构成了类的继承体系
如果对象的类型无法在编译器确定,那么就应该使用类型信息查询的方式来探知,而不要直接比较类对象,因为某些对象可能实现了消息转发功能,比如代理:通常情况下,在代理对象上调用class方法,返回的是代理对象本身(此类是NSProxy的子类),而非接收代理的对象所属的类。

上一篇 下一篇

猜你喜欢

热点阅读