-
属性的理解:
属性是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中抛出异常(暴力,不推荐)
-
(BOOL)isKindOfClass:(Class)aClass; 接收者是否是aClass类的实例或者从这个类继承的任何类的实例。如果是返回yes
-
(BOOL)isMemberOfClass:(Class)aClass;接收者是否是aClass的实例,如果是返回yes。
-
技巧九:对象关联
关联对象,相当于临时的动态添加属性,非不得已不用
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
- 1.先征询接收者所属的类,看是否能动态添加方法,以处理当前这个未知的”选择子“,这个过程也叫”动态方法解析“(dynamic method resolution)
+(BOOL)resolveInstanceMethod:(SEL)sel(动态添加实例方法)
+(BOOL)resolveClassMethod:(SEL)sel
-
2.涉及完整的消息转发机制,首先请接收者看看有没有其他对象可以处理这条消息,如果没有备援的接收者,会把消息封装成NSInvocation对象,再给最后一次机会解决当前问题。
-
3.查找备援接收者:
-(id)forwardingTargetForSelector:(SEL)aSelector(我们无法经由这一步操作转发的消息)
- 4.最后一步:
-(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的子类),而非接收代理的对象所属的类。
网友评论