知识铺垫-OC对象以及方法调用
Category的实现原理
Category和Class Extension 类扩展的区别
Category中load方法是什么时候调用的?
使用关联对象为分类添加成员变量
知识铺垫-OC对象以及方法调用
OC对象主要分为三种:
instance对象(实例对象)
class对象(类对象)
meta-class对象(元类对象)
他们之间的调用关系~~~~~额, 一图胜千言 更何况是两张图呢......
image image-
instance对象的
isa
指向class对象,class对象的isa指向 meta-class,而meta-class的isa指向基类的meta-class -
class对象的superclass指向父类的class对象,如果没有父类,superclass指针为nil。meta-class的superclass指向父类的meta-class,基类的元类的superclass指向基类的class(这个比较特殊)
重点:
instance调用对象方法的轨迹
isa指针找到class,如果方法不存在,就通过superclass找父类,依次类推
class调用类方法的轨迹
isa指针找到meta-class,如果方法不存在,就通过superclass找父类
Category的实现原理
- 当应用程序编译完之后所有的分类数据其实都是存储在了对应的
Category_t
类型的结构体中了,该结构体中存储着分类的对象方法、类方法、属性和协议信息。通过Runtime的源码我们可以看到该结构体的具体信息大致如下所示:
- 在程序运行的时候,通过Runtime加载某个类的所有Category数据,同时将Category中的方法、属性、协议数据合并到一个大数组中,后来参与编译的Category数据,会在数组的前面。
- 将合并后的分类数据包括方法、属性、协议等信息,插入到类原来数据的前面,所以这也就造成了如果分类和类中有相同的方法,调用的时候会优先调用分类的方法,而且如果多个分类中有相同的名称方法则会优先调用最后参与编译的
image
例如上图,如果这两个分类中有相同名称的方法则最终会调用红框中的
因为源码太多 就不在一一贴图解释了,这里列举了源码的阅读顺序,感兴趣的朋友可以自己尝试着去仔细分析一下
objc-os.mm
_objc_init
map_images
map_images_nolock
objc-runtime-new.mm
_read_images
remethodizeClass
attachCategories
attachLists
realloc、memmove、 memcpy
Category和Class Extension 类扩展的区别
Category
是在运行时,才会将数据合并到类信息中。在分类中只能添加“方法”,不能增加成员变量。
Extension
既可以对类添加属性,成员变量又可以添加方法,在程序编译的时候,它的数据就已经包含在类信息中了,一般用来隐藏类的私有方法和属性
Category中load方法是什么时候调用的?
imageload方法在程序启动加载类信息的时候就会调用。它是根据函数地址直接调用的,而不是通过消息机制的。调用顺序如下:
-
先调用类的+load
- 按照编译先后顺序调用(先编译,先调用)
- 调用子类的+load之前会先调用父类的+load
-
再调用分类的+load
- 按照编译的先后顺序调用(先编译,先调用)
load、initialize的区别
答:区别在于调用方式和调用时刻
调用方式:load是根据函数地址直接调用的,而initialize是通过objc_msgSend()调用
调用时刻:load是runtime加载类、分类的时候调用(只会调用1次),initialize是类第一次接收到消息
的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)
调用顺序:先调用类的load方法,先编译那个类,就先调用哪个类的load。在调用load之前会先调用父类的load方法。分类中load方法不会覆盖本类的load方法,先编译的分类优先调用load方法。initialize先初始化父类,之后再初始化子类。如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次),如果分类实现了+initialize,就覆盖类本身的+initialize调用。
使用关联对象为分类添加成员变量
我们来看下面一段代码:
#import "GSPerson.h"
@interface GSPerson (Utility)
@property (nonatomic, assign) int height;
@end
我们为GSPerson
对象添加了一个分类,并且在分类中设置了一个height
的属性,下面我们来使用一下该分类对象:
我们为height
赋值20 ,运行起来之后竟然奔溃了,并且提示我们
reason: '-[GSPerson setHeight:]: unrecognized selector sent to instance 0x1006259a0'
为什么会这样呢?
因为我们在分类中添加了属性之后
系统只是为我们生成了get方法
和set方法
的声明,并没有为我们生成成员变量和方法的实现
我们可以通过关联对象
来为分类添加成员变量.
#import "GSPerson+Utility.h"
#import <objc/runtime.h>
@implementation GSPerson (Utility)
-(void)setHeight:(int)height{
//key值只要是一个指针即可,我们可以传入@selector(name)
objc_setAssociatedObject(self, @selector(height), @(height), OBJC_ASSOCIATION_ASSIGN);
//或者
//objc_setAssociatedObject(self, @"height", @(height), OBJC_ASSOCIATION_ASSIGN);
}
-(int)height{
//_cmd == @selector(height)
return [objc_getAssociatedObject(self, _cmd) intValue];
//或者
//return [objc_getAssociatedObject(self, @"height") intValue];
}
@end
此时我们就为GSPerson
对象添加了height
属性.
下面我们来看看这两个方法:
/*
参数一:id object : 获取哪个对象里面的关联的属性。
参数二:void * == id key : 什么属性,与objc_setAssociatedObject中的key相对应,即通过key值取出value。
*/
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
/*
参数一:id object : 给哪个对象添加属性,这里要给自己添加属性,用self
参数二:void * == id key : 属性名,根据key获取关联对象的属性的值,在objc_getAssociatedObject中通过此key获得属性的值并返回。
参数三:id value : 关联的值,也就是set方法传入的值给属性去保存。
参数四:objc_AssociationPolicy policy : 策略,属性以什么形式保存。
*/
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy)
其中参数四策略是个枚举值
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, // 指定一个弱引用相关联的对象
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 指定相关对象的强引用,非原子性
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, // 指定相关的对象被复制,非原子性
OBJC_ASSOCIATION_RETAIN = 01401, // 指定相关对象的强引用,原子性
OBJC_ASSOCIATION_COPY = 01403 // 指定相关的对象被复制,原子性
};
具体的对应关系如下所示:
image
网友评论