1、Category
1.1、原理
#import "FQPeople+Test.h"
@implementation FQPeople (Test)
+ (void)test{
}
- (void)test1{
}
@end
-
分类的对象方法test1存放在类对象FQPeople中
-
分类的类方法test存在FQPeople元类中
使用:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc FQPeople+Test.m
在编译时生成c++代码
每一个分类都会产生一个结构体
XFPeople XFPeople+Category1 XFPeople+Category2
产生两个结构体
struct _category_t {
const char *name;//类名FQPeople
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_t中
在运行时通过runtime动态的将分类的方法合并到类对象、元类对象中 在程序启动的时候就合并
而不是在使用该分类 或者编译时调用的时候才合并
通过这个流程得知 依次插入
XFPeople XFPeople+Category1 XFPeople+Category2
着三个中都有-(void)test{}方法,会先从分类中找方法,如果分类中没有再从对象中找方法 (数组遍历从索引0开始的)
源码:
至于是先执行 XFPeople+Category1 或者是 XFPeople+Category2中的方法,则存在不确定性,这个要看哪个先编译,最后编译的放在最前面
总结整体流程:
1、通过runtime加载某一个类的所有分类数据
2、把所有的category的方法属性协议合并到一个数组中(后编译的分类数据会放在数组的前面)
3、将合并后的分类数据(方法,属性,协议)插入到类原来数据的前面
最后粘贴一下运行时源码
最后粘贴一下运行时源码
/*
类对象,分类列表
cls XQPeople
cats = [category_t(Test),category_t(Eat)]
*/
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
// fixme rearrange to remove these intermediate allocations
///方法数组
/**
[
[method-t,method-t],[method-t,method-t]
]
*/
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
///属性数组
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
///协议数组
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists));
// Count backwards through cats to get newest categories first
int mcount = 0;
int propcount = 0;
int protocount = 0;
int i = cats->count;
bool fromBundle = NO;
while (i--) {
///取出某一个分类
auto& entry = cats->list[i];
///取出分类中的对象方法(或者元类方法) 最终把所有的分类方法列表放入mlists大数组中
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
///获取类对象中的数据
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
///将所有分类的方法放到类对象方法中
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
///将所有分类的属性方法放到类对象方法中
rw->properties.attachLists(proplists, propcount);
free(proplists);
///将所有分类的属协议方法放到类对象方法中
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
///对象方法数量+分类方法数量
uint32_t newCount = oldCount + addedCount;
///重新分配内存
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
//array()->lists 原来的方法列表
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
///addedLists 所有的分类方法列表
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
...
1.2、扩展(类扩展)
@interface FQPeople()
@end
@implementation FQPeople
@end
类扩展是在编译的时候就把扩展的信息合并到类里面去,而分类是在运行时合并的
2、Category实现原理(m)
-
category 编译之后的底层时显示一个 struct category_t 类型的结构体
里面存放的是分类的对象方法列表 类方法列表 协议方法列表 属性列表 类名 ,类对象
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;
};
- 运行时(程序运行或者启动的时候)会将分类的数据合并到原来的类信息中(类对象、元类对象)
3、Category 和class extension 的区别(m)
-
class extension是在编译的时候就把扩展的信息合并到类里面去,
-
Category是在运行时将数据合并到类信息中
4、load initialize
4.1、load的原理
- load方法在在runtime加载类、分类的时候调用
相当于运行时程序一旦启动的时候进行调用,加载类的时候调用类的load 加载分类的时候调用分类的load,
- 每个类、分类的+load,在程序运行过程中只调用一次;
4.1.1、不存在继承关系的load
证明
@implementation People (Test1)
+ (void)load{
NSLog(@"%s",__func__);
}
+ (void)test{
NSLog(@"%s",__func__);
}
@end
@implementation People (Test2)
+ (void)load{
NSLog(@"%s",__func__);
}
+ (void)test{
NSLog(@"%s",__func__);
}
@end
@implementation People
+ (void)load{
NSLog(@"%s",__func__);
}
+ (void)test{
NSLog(@"%s",__func__);
}
直接运行
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}
+[People load]
+[People(Test2) load]
+[People(Test1) load]
可以看出 只要有load方法在程序启动的时候就会调用
void printMethodNamesOfCalss(Class cls){
unsigned int count;
Method *methodList = class_copyMethodList(cls, &count);
NSMutableString *methodsNames = [NSMutableString string];
for (int i = 0; i<count; i++) {
Method method = methodList[i];
NSString *methodsName = NSStringFromSelector(method_getName(method));
[methodsNames appendString:methodsName];
[methodsNames appendString:@", "];
}
free(methodList);
NSLog(@"%@ %@",cls,methodsNames);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
printMethodNamesOfCalss(object_getClass([People class]));
[People test];
}
return 0;
}
程序启动加载时(runtime)调用load
+[People load]
+[People(Test2) load]
+[People(Test1) load]
///打印出对象和分类对象的方法列表 可以看得有多个load 多个test
People load, test, load, test, load, test,
///[People test]; 只调用了Test2的test方法
+[People(Test2) test]
调用顺序
- 类的load方法
疑问:同样是类方法,且都在类方法列表中存在 为什么load调用了三次 而test只调用了分类中的呢?
void call_load_methods(void){
do {
//先调用类的load方法
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE 再调动分类的load方法
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
static void call_class_loads(void)
{
int i;
// Detach current loadable list. 所有有load的类的列表
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// Call all +loads for the detached list.遍历load类列表
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
// 取出该load的load类方法指针
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]
", cls->nameForLogging());
}
///直接调用该load方法
(*load_method)(cls, SEL_load);
}
// Destroy the detached list.
if (classes) free(classes);
}
typedef void(*load_method_t)(id, SEL);
struct loadable_class {
Class cls; // may be nil
IMP method;//这个里面存放的就是类load方法
};
struct loadable_category {
Category cat; // may be nil
IMP method;//这个里面存放的就是分类load方法
};
答:为什么load会执行三次
因为在运行时时期 会遍历所有带有load方法的类,直接拿到load方法的地址 直接调用
而+(void)test{}呢?
这个方法是通过发送消息
objc_msgSend(objc_getClass("People"), sel_registerName("test"));
4.1.2、存在继承关系的load
-
先调用类load方法 1000个类的话,那先调用那个类呢?
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 递归调用 先调用s父类 schedule_class_load(cls->superclass); ///把父类加入到包含load类的列表中 列表中存储类对象 add_class_to_loadable_list(cls); cls->setInfo(RW_LOADED); }
首先会先调用父类的load方法 在调用子类的load方法
如果不存在继承关系呢?
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertWriting();
///按加载顺序加入数组
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
///加载子类之前先加载父类
schedule_class_load(remapClass(classlist[i]));
}
///按加载顺序加入数组
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
///直接把分类加入数组最后面
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
realizeClass(cls);
assert(cls->ISA()->isRealized());
add_category_to_
}
总结:
1、先调用类的load方法
- 按照编译顺序调用 (先编译,先调用)
- 调用子类的+load方法之前,先调用父类的+load方法
2、再调用分类的load方法
- 按照编译顺序调用 (先编译,先调用)
4.2、initialize
[ɪˈnɪʃəlaɪz]
-
+initialize方法会在类第一次接收到消息的时候调用 且每个类只会初始化一次
比如:[Student alloc]; 消息机制objc_msgSend方法 调用的时分类的方法
[Student alloc];
+[People(Test1) initialize]
+[Student(Test2) initialize]
- 会先调用父类的initilize(分类)在调用子类的initialize(先分类)的方法
如果父类initialize 没有调用的话会调用,如果已经调用了那就不能在子类初始化的时候再调用
向子类发送消息 会先向父类发送消息
objc_msgSend(object_getClass([People alloc]), @selector(initialize))
objc_msgSend(object_getClass([Student alloc]), @selector(initialize))
证明:
Method class_getInstanceMethod(Class cls, SEL sel)
{
lookUpImpOrNil(cls, sel, nil,
NO/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
IMP lookUpImpOrNil(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
}
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
///这个方法是否需要初始化 &&如果没有初始化
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;
// Make sure super is done initializing BEFORE beginning to initialize cls.
// See note about deadlock above.
/// 如果存在父类,并且没有初始化 m那就初始化父类
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
_class_initialize(supercls);
}
#if __OBJC2__
@try
#endif
{
///真真的初始化
callInitialize(cls);
}
}
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
5、initialize和load的区别
+initialize 是通过objc_msgSend 方法进行调用的 +load在程序加载时直接找到函数指针进行调用的
所以+initialize有以下特点
- 如果子类没有实现+initialize方法会调用父类的initialize(所以父类的initialize会被调用多次)
- 如果分类实现了+initialize方法会覆盖类本身的initialize调用
@implementation People
+ (void)initialize{
NSLog(@"%s",__func__);
}
@end
@implementation Student //(继承People)
+ (void)initialize{
NSLog(@"%s",__func__);
}
@end
@implementation Animal //(继承People)
+ (void)initialize{
NSLog(@"%s",__func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
[Student alloc];
[Animal alloc];
}
return 0;
}
+[People initialize]
+[Student initialize]
+[Animal initialize]
为什么会是这个结果: [Student alloc]; 是会先去判断父类是否已初始化
没有所以调用父类的
objc_msgSend(object_getClass([People alloc]), **@selector**(initialize));
在执行自己的
objc_msgSend(object_getClass([Student alloc]), **@selector**(initialize));
而[Animal alloc]; 时父类People已经初始化了所以只执行了自己的
objc_msgSend(object_getClass([Animal alloc]), **@selector**(initialize));
@implementation People
+ (void)initialize{
NSLog(@"%s",__func__);
}
@end
@implementation Student //(继承People)
@end
@implementation Animal //(继承People)
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
[Student alloc];
[Animal alloc];
}
return 0;
}
+[People initialize]
+[People initialize]
+[People initialize]
为什么子类没有initialize会执行多次呢?
[Student alloc];
if (Student是否需要初始化&&Student没有初始化) {
if (People是否需要初始化&&People有没有没有初始化) {
objc_msgSend(object_getClass([People alloc]), @selector(initialize))
}
objc_msgSend(object_getClass([Student alloc]), @selector(initialize))
}
[Animal alloc];
if (Animal是否需要初始化&&Animal没有初始化) {
if (People是否需要初始化&&People有没有没有初始化) {
objc_msgSend(object_getClass([People alloc]), @selector(initialize))
}
objc_msgSend(object_getClass([Animal alloc]), @selector(initialize))
}
也就是
objc_msgSend(object_getClass([People alloc]), @selector(initialize))
objc_msgSend(object_getClass([Student alloc]), @selector(initialize))
根本找不到Student 的initialize方法 去父类中找所以People的initialize被调用
objc_msgSend(object_getClass([Animal alloc]), @selector(initialize))
根本找不到Animal 的initialize方法 去父类中找所以People的initialize被调用
注意点 虽然[People initialize] 被执行了三次但并不代表父类被初始化了三次
他是第一次对父类,其他两次是对子类进行的初始化
6、Category中有+load方法吗?load方法是什么时候调用的?load方法能继承吗?(m)
- 有
- 在runtime加载类、分类的时候调用这个方法
- 可以继承 如果子类没有load放会调用父类的load 发送消息的流程
[Student load]; Student中没有 调用父类的 父类有分类调用分类的
严格来讲不会主动调用load方法
7、initialize和load的区别(m)
-
调用方式的区别
- load是直接通过函数指针的方式去调用 initialize是使用的消息机制objc_msgSend
-
调用时机不同
- load是在runtime加载类、分类的时候调用且之后调用一次
- initialize 是在类第一次接收消息的时候调用,而且某一个类只会initialize一次,但是父类的initialize可能会被调用多次
8、initialize和load的调用顺序(m)
-
load先调用类的load方法 且先编译的类的先调用 且调用子类的load之前会先调用父类的load方法
-
在调用分类的load方法 且先编译的分类的先调用
-
initialize 先初始化父类 在初始化子类 如果子类没有initialize会调用父类的