苹果源码https://opensource.apple.com/tarballs/objc4/
Category的底层结构
struct category_t {
const char *name;
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);
protocol_list_t *protocolsForMeta(bool isMeta) {
if (isMeta) return nullptr;
else return protocols;
}
};
Category的加载处理过程
- objc-os.mm
void _objc_init(void)
{
.......
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
runtime入口函数,map_images镜像文件
map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
mutex_locker_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}
处理由dyld映射的镜像文件
- objc-runtime-new.mm
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[]){
......
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
...
}
读取镜像文件
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses){
...
// Discover categories.
if (didInitialAttachCategories) {
for (EACH_HEADER) {
load_categories_nolock(hi);
}
}
...
}
加载分类
static void load_categories_nolock(header_info *hi) {
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
size_t count;
auto processCatlist = [&](category_t * const *catlist) {
for (unsigned i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
locstamped_category_t lc{cat, hi};
}
// Process this category.
if (cls->isStubClass()) {
.....
}else{
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties) {
if (cls->isRealized()) {
attachCategories(cls, &lc, 1, ATTACH_EXISTING);
} else {
objc::unattachedCategories.addForClass(lc, cls);
}
}
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties)) {
if (cls->ISA()->isRealized()) {
attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
} else {
objc::unattachedCategories.addForClass(lc, cls->ISA());
}
}
}
}
}
核心方法Class cls = remapClass(cat->cls);重建Class的方法列表
attachCategories(cls, &lc, 1, ATTACH_EXISTING);
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags){
for (uint32_t i = 0; i < cats_count; i++) {
auto& entry = cats_list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
if (mcount == ATTACH_BUFSIZ) {
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rwe->methods.attachLists(mlists, mcount);
mcount = 0;
}
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
if (propcount == ATTACH_BUFSIZ) {
rwe->properties.attachLists(proplists, propcount);
propcount = 0;
}
proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
}
protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
if (protolist) {
if (protocount == ATTACH_BUFSIZ) {
rwe->protocols.attachLists(protolists, protocount);
protocount = 0;
}
protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
}
}
}
附加分类的方法列表,属性列表,协议列表到class
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]));
}
....
}
结论:
通过Runtime加载某个类的所有Category数据
把所有Category的方法、属性、协议数据,合并到一个大数组中
后面参与编译的Category数据,会在数组的前面将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面
面试题
Category里面的方法存放在哪里?
一个类所对应的分类下的对象方法,存放在该类的类对象的方法列表里面。
一个类所对应的分类下的类方法,会存放在该类的元类对象的方法列表里面
Category的实现原理
Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息
在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)
Category和Class Extension的区别是什么?
Class Extension在编译的时候,它的数据就已经包含在类信息中
Category是在运行时,才会将数据合并到类信息中
+load方法
源码解读:
- objc-os.mm
void _objc_init(void)
{
.......
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
runtime入口函数, load_images镜像文件的load函数
void
load_images(const char *path __unused, const struct mach_header *mh)
{
......
// Discover load methods
{
.....
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
prepare_load_methods对load方法的准备,call_load_methods()调用load方法
void prepare_load_methods(const headerType *mhdr)
{
....
如果没有父类按照编译顺序获取class列表
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
如果有父类就先添加父类
schedule_class_load(remapClass(classlist[i]));
}
按照编译顺序添加分类列表
category_t * const *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
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}
....
添加分类的load方法到loadable_list
add_category_to_loadable_list(cat);
}
}
static void schedule_class_load(Class cls)
{
....
// Ensure superclass-first ordering
递归调用,先添加父类的class对象
schedule_class_load(cls->superclass);
添加class对象中的load方法到loadable_list
add_class_to_loadable_list(cls);
...
}
添加class中的load方法
void call_load_methods(void)
{
....
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
} while (loadable_classes_used > 0 || more_categories);
...
}
结论:
+load方法会在runtime加载类、分类时调用
每个类、分类的+load,在程序运行过程中只调用一次
调用顺序
1. 先调用类的+load
按照编译先后顺序调用(先编译,先调用)
调用子类的+load之前会先调用父类的+load
2. 再调用分类的+load
按照编译先后顺序调用(先编译,先调用)
+load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用
面试题
Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?
- 有load方法
- load方法在runtime加载类、分类的时候调用
- load方法可以继承,但是一般情况下不会主动去调用load方法,都是让系统自动调用
+initialize方法
源码解读
- objc-msg-arm64.s
objc_msgSend //汇编 - objc-runtime-new.mm
Class class_initialize(Class cls, id obj)
{
...
return initializeAndMaybeRelock(cls, obj, runtimeLock, false);
}
类的initialize方法
static Class initializeAndMaybeRelock(Class cls, id inst,
mutex_t& lock, bool leaveLocked)
{
...
initializeNonMetaClass(nonmeta);
...
}
void initializeNonMetaClass(Class cls){
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
递归调用,先调用父类initialize方法
initializeNonMetaClass(supercls);
}
...
if (reallyInitialize) {
@try
{
调用当前类的initialize方法
callInitialize(cls);
}
}
....
else if (cls->isInitialized()) {
如果当前类已经initialize就return
return;
}
结论:
+initialize方法会在类第一次接收到消息时调用
调用顺序
先调用父类的+initialize,再调用子类的+initialize
(先初始化父类,再初始化子类,每个类只会初始化1次)+initialize和+load的很大区别是,+initialize是通过objc_msgSend进行调用的,所以有以下特点
如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
如果分类实现了+initialize,就覆盖类本身的+initialize调用
面试题:
load、initialize方法的区别什么?它们在category中的调用的顺序?以及出现继承时他们之间的调用过程?
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方法)