Category的底层结构

Category的加载处理过程
- 通过Runtime加载某个类的所有Category数据
- 把所有Category的方法、属性、协议数据,合并到一个大数组中后面参与编译的Category数据,会在数组的前面
- 将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面
1)、category的方法没有“完全替换掉”原来类已经有的方法,也就是说如果category和原来类都有methodA,那么category附加完成之后,类的方法列表里会有两个methodA
2)、category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,这也就是我们平常所说的category的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会罢休_,殊不知后面可能还有一样名字的方法。
+load方法
+load方法会在runtime加载类、分类时调用每个类、分类的+load,在程序运行过程中只调用一次
调用顺序
1.先调用类的+load
- 按照编译先后顺序调用(先编译,先调用)
- 调用子类的+load之前会先调用父类的+load
2.再调用category的+load
先编译的分类,优先调用load(先编译,先调用)
虽然对于+load的执行顺序是这样,但是对于“覆盖”掉的方法,则会先找到最后一个编译的category里的对应方法。
+initialize方法
+initialize方法会在类第一次接收到消息时调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)
调用顺序
先调用父类的+initialize
再调用子类的+initialize
(先初始化父类,再初始化子类,每个类只会初始化1次)
+initialize和+load的很大区别是,+initialize是通过objc_msgSend进行调用的,所以有以下特点
如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
如果分类实现了+initialize,就覆盖类本身的+initialize调用,类不会调用+initialize
调用方式
1> load是根据函数地址直接调用
2> initialize是通过objc_msgSend调用
在category中添加和对象关联的值?
//添加关联对象
void objc_setAssociatedObject(id object, const void * key,id value, objc_AssociationPolicy policy)
//获得关联对象
id objc_getAssociatedObject(id object, const void * key)
//移除所有的关联对象
void objc_removeAssociatedObjects(id object)
@interface MJPerson (Test)
@property (copy, nonatomic) NSString *name;
@end
#import <objc/runtime.h>
@implementation MJPerson (Test)
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name
{
// 隐式参数
// _cmd == @selector(name)
return objc_getAssociatedObject(self, _cmd);
}
//把方法地址作为KEY,也可以用其他变量作为Key
//static修饰的变量在编译的时候值就应该确定
static const void *NameKey = &NameKey;
static const void *WeightKey = &WeightKey;
void *objc_destructInstance(id obj)
{
if (obj) {
Class isa_gen = _object_getClass(obj);
class_t *isa = newcls(isa_gen);
// Read all of the flags at once for performance.
bool cxx = hasCxxStructors(isa);
bool assoc = !UseGC && _class_instancesHaveAssociatedObjects(isa_gen);
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
if (!UseGC) objc_clear_deallocating(obj);
}
return obj;
}
runtime的销毁对象函数objc_destructInstance里面会判断这个对象有没有关联对象,如果有,会调用_object_remove_assocations做关联对象的清理工作
struct _category_t {
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};
category添加成员变量报错:Instance variables may not be placed in categories.
category不能添加成员变量的原因:
objc_class结构体大小是固定的,不可能往这个结构体中添加数据,只能修改。所以ivars指向的是一个固定区域,只能修改成员变量值,不能增加成员变量个数。
methodList是是一个二维数组,所以可以修改*methodLists的值来增加成员方法.
category可以通过关联对象的方式添加属性(property),但是不能添加实例变量(instance variable)。
category是无法添加实例变量的(因为在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,这对编译型语言来说是灾难性的)

网友评论