Category总结
Category的底层结构
定义在objc-runtime-new.h中
struct category_t {
const char *name;//类名 LQPerson
classref_t cls;//类指针
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
生成C++代码中可以看到如下的结构体
static struct _category_t _OBJC_$_CATEGORY_LQPerson_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"LQPerson",
0, // &OBJC_CLASS_$_LQPerson,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_LQPerson_$_Eat,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_LQPerson_$_Eat,
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_LQPerson_$_Eat,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_LQPerson_$_Eat,
};
Category的加载处理过程
- 通过Runtime加载某个类的所有Category数据
- 把所有Category的方法、属性、协议数据,合并到一个大数组中
后面参与编译的Category数据,会在数组的前面 - 将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面
源码解读顺序
objc-os.mm
_objc_init
map_images
map_images_nolock
objc-runtime-new.mm
_read_images
remethodizeClass
attachCategories
attachLists
realloc、memmove、 memcpy
分类方法如何附加到类对象中,请看如下函数调用
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_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--) {//注意这里是i--,也就是把最后面的先拿出来,也就是最后面的分类
//取出某个分类
auto& entry = cats->list[i];
//取出分类中的对象方法列表
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 原来的方法列表, 往后挪动了addedCount
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
//addedLists 所有分类的方法列表
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
-
move
是直接挪动 能够保证以前的数据完整的挪动过去 -
mempy
是一个一个的拷贝,随便覆盖,不会存在数据丢失
category的方法
不是真的覆盖方法,而是顺序在前面了,找到了分类的方法就不再去找了,即使类里面有这个方法也没有机会调用
分类和类扩展的区别
类扩展:编译就已经存在里面了
分类:runtime
+load方法
调用时机:
+load方法会在runtime加载类、分类时调用
每个类、分类的+load,在程序运行过程中只调用一次
调用顺序
先调用类的+load
按照编译先后顺序调用(先编译,先调用)
调用子类的+load之前会先调用父类的+load
再调用分类的+load
按照编译先后顺序调用(先编译,先调用)
objc4源码解读过程:objc-os.mm
_objc_init
load_images
prepare_load_methods
schedule_class_load
add_class_to_loadable_list
add_category_to_loadable_list
call_load_methods
call_class_loads
call_category_loads
(*load_method)(cls, SEL_load)
+load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用
static void call_class_loads(void)
{
int i;
// Detach current loadable list.
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.
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;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
(*load_method)(cls, SEL_load);//+load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用
}
// Destroy the detached list.
if (classes) free(classes);
}
+initialize方法
+initialize方法会在类第一次接收到消息时调用
调用顺序
先调用父类的+initialize,再调用子类的+initialize
(先初始化父类,再初始化子类,每个类只会初始化1次)
objc4源码解读过程
objc-msg-arm64.s
objc_msgSend
objc-runtime-new.mm
class_getInstanceMethod
lookUpImpOrNil
lookUpImpOrForward
_class_initialize
callInitialize
objc_msgSend(cls, SEL_initialize)
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");//说明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方法)
QA
- Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?
Category中有load方法,load方法在程序启动装载类信息的时候就会调用。load方法可以继承。调用子类的load方法之前,会先调用父类的load方法
- load、initialize的区别,以及它们在category重写的时候的调用的次序。
区别在于调用方式和调用时刻
调用方式:load是根据函数地址直接调用,initialize是通过objc_msgSend调用
调用时刻:load是runtime加载类、分类的时候调用(只会调用1次),initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)
调用顺序:先调用类的load方法,先编译那个类,就先调用load。在调用load之前会先调用父类的load方法。分类中load方法不会覆盖本类的load方法,先编译的分类优先调用load方法。initialize先初始化父类,之后再初始化子类。如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次),如果分类实现了+initialize,就覆盖类本身的+initialize调用。