Category底层原理
Category可以把一个类的功能拆解成很多模块
创建一个类,并创建两个分类
分类编译时底层编译成的代码:
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; // 属性列表
};
每一个分类对应一个结构体对象
如:
#import "MJPerson+Test.h"
@implementation MJPerson (Test)
- (void)run
{
NSLog(@"MJPerson (Test) - run");
}
- (void)test
{
NSLog(@"test");
}
+ (void)test2
{
}
编译成C++代码
static struct _category_t _OBJC_$_CATEGORY_MJPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"MJPerson",
0, // &OBJC_CLASS_$_MJPerson,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_MJPerson_$_Test,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_MJPerson_$_Test,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_MJPerson_$_Test,
};
// 对象方法
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_MJPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"test", "v16@0:8", (void *)_I_MJPerson_Test_test}}
};
// 类方法
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_MJPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"test2", "v16@0:8", (void *)_C_MJPerson_Test_test2}}
};
// 属性列表
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[2];
} _OBJC_$_PROP_LIST_MJPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
2,
{{"weight","Ti,N"},
{"height","Td,N"}}
};
分类里的属性、方法、协议等最后也是通过runtime动态合并到类对象,元类对象中
具体步骤:
源码下载地址 https://opensource.apple.com/tarballs/objc4
如方法的合并:
runtime 会将Person的所有分类的方法列表先合并到一个列表里面,然后再通过内存移动插入到原来Person方法列表的前面
至于Test1 和 Test2 谁在前 谁在后,根据编译循序来的因为 合并分类列表的代码为
int mcount = 0;
int propcount = 0;
int protocount = 0;
int i = cats->count; // 分类结构数组
bool fromBundle = NO;
while (i--) { // 使先编译的后调用
auto& entry = cats->list[I];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;// 合并
fromBundle |= entry.hi->isBundle();
}
通过源码可知,后编译的在前面,至于编译循序可以在下面调整
所以如果在Person 和 两个分类中都有一个相同的方法 如 run,以上图的编译循序 是执行 Test1中的方法。这个并不是方法覆盖,而是,方法查找的时候会先查到Test1中的方法
和类扩展的区别 Extension(OC)
类扩展是在编译的时候就将方法 属性等加入到原先的方法、属性列表中了
分类添加属性相关
1、当我们给一个类添加属性的时候如
@property (nonatomic, assign) int age;
// 给一个类添加age 属性
会默认实现下面3个步骤
// 1、声明一个成员变量
{
int _age;
}
//2、 声明set 和 get 方法
- (void)setAge:(int)age;
- (int)age;
//3、实现get 和set 方法
- (void)setAge:(int)age {
_age = age;
}
- (int)age {
return _age;
}
2、当我们给分类添加属性时,默认只有方法的声明
@property (assign, nonatomic) int weight;
//默认声明
- (void)setWeight:(int)weight;
- (int)weight;
但是没有实现,所以可以调用,但是会报错找不到方法
Person *person = [Person new];
person.weight = 10;
NSLog(@"%d",person.weight);
//-[Person weight]: unrecognized selector sent to instance 0x10067f080'
我们如果手动加上成员变量 实现set 和get 方法
编译时就会报错,分类中不能添加成员变量
从上面的分类编译成的底层代码也可以发现,根本没有成员变量列表,
下图是一个普通类的底层结构。有个成员变量列表
3、给分类添加关联对象实现类似成员变量的功能
实现属性的set 和 get 方法
// 地址值
static const void *PersonNameKey = &PersonNameKey;
/**
加上static 防止外面访问,否则别的地方
通过 extern const void *PersonNameKey; 可以访问到
*/
- (void)setName:(NSString *)name {
//objc_AssociationPolicy 关联策略 类似于用什么修饰 copy assign strong 等
// objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy)
objc_setAssociatedObject(self, PersonNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
return objc_getAssociatedObject(self, PersonNameKey);
}
其中const void * _Nonnull key 是一个地址值可以有很多种办法生成
//static const char LQNameKey;
- (void)setName:(NSString *)name {
//1、 使用get方法的@selector
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
// 2、使用属性名称
objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_COPY_NONATOMIC);
// 3、使用一个字符
objc_setAssociatedObject(self, &LQNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
objc_AssociationPolicy 对应的修饰符
关联对象的原理
void objc_setAssociatedObject(id object, const void * key,
id value,
objc_AssociationPolicy policy)
实现关联对象技术的核心对象有
AssociationsManager
AssociationsHashMap
ObjectAssociationMap
ObjcAssociation
runtime 管理着一个全局的AssociationsManager,内部管理一个map(AssociationsHashMap),这个map的key是根据object生成的,value对应的是另一个map(ObjectAssociationMap),objectMap的key就是上面方法传进来的key,value对应ObjcAssociation对象,ObjcAssociation内部包含policy和真正的value
+load方法
一、+load 方法会在runtime加载类和分类时调用
二调用顺序
1.先调用类的+load
按照编译顺序调用(先编译,先调用)
调用子类的+load方法之前,如果父类的+load没调用过就先调用父类的+load方法
2、所有类的+load方法调用完,再调用分类的+load
按照编译顺序调用(先编译,先调用)(没有父类,子类之分)
3、底层代码
先调用父类的+load 方法的原因 准备调用
prepare_load_methods会调用下面的代码
static void schedule_class_load(Class cls)
{
if (!cls) return;
assert(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// Ensure superclass-first ordering
schedule_class_load(cls->superclass);//递归调用
add_class_to_loadable_list(cls);// 加入类load列表中
cls->setInfo(RW_LOADED);
}
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads(); // 调用类的
}
// 2. Call category +loads ONCE
more_categories = call_category_loads(); // 调用分类的
// 3. Run more +loads if there are classes OR more untried categories
}
call_class_loads 简化为
static void call_class_loads(void)
{
struct loadable_class *classes = loadable_classes;
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
(*load_method)(cls, SEL_load);
}
}
call_category_loads 调用分类的+load
static bool call_category_loads(void) {
for (i = 0; i < used; i++) {
Category cat = cats[i].cat;
load_method_t load_method = (load_method_t)cats[i].method;
Class cls;
if (!cat) continue;
(*load_method)(cls, SEL_load); // 直接通过函数指针调用
}
load_method_t
struct loadable_class {
Class cls; // may be nil
IMP method; // load方法
};
struct loadable_category {
Category cat; // may be nil
IMP method; //load方法
};
所以load方法是直接找到,然后通过函数指针调用的,不像上面的run方法 通过objc_msgSend调用,通过isa指针找方法列表
+initialize方法
调用顺序
+initialize方法会在类第一次接收到消息时调用
1、先调用父类的initialize, 再调用子类的+initialize
2、(先初始化父类,再初始化子类,每个类只会初始化1次)
源码解读过程
objc4源码解读过程
objc-msg-arm64.s
objc_msgSend
objc-runtime-new.mm
class_getInstanceMethod
lookUpImpOrNil
lookUpImpOrForward
_class_initialize
callInitialize
objc_msgSend(cls, SEL_initialize)
源码
每次调用objc_msgSend 会调用
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
if (initialize && !cls->isInitialized()) { // 需要初始化,没有初始化
runtimeLock.unlockRead();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
}
}
void _class_initialize(Class cls) // 初始化
{
assert(!cls->isMetaClass());
Class supercls;
bool reallyInitialize = NO;
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) { // 如果父类没有初始化,先递归初始化父类
_class_initialize(supercls);
}
callInitialize(cls);
}
// 最终初始化 也是通过objc_msgSend
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
注意
+initialize和+load的很大区别是,+initialize是通过objc_msgSend进行调用的,所以有以下特点
如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
如果分类实现了+initialize,就覆盖类本身的+initialize调用
+load 和 initialize 总结
load、initialize方法的区别什么?
1.调用方式
1> load是根据函数地址直接调用
2> initialize是通过objc_msgSend调用
2.调用时刻
1> load是runtime加载类、分类的时候调用(只会调用1次)
2> initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)
load、initialize的调用顺序?
1.load
1> 先调用类的load
a) 先编译的类,优先调用load
b) 调用子类的load之前,会先调用父类的load
2> 再调用分类的load
a) 先编译的分类,优先调用load
2.initialize
1> 先初始化父类
2> 再初始化子类(可能最终调用的是父类的initialize方法)