iOS底层原理

iOS底层原理 - Category实现原理(二)

2020-04-26  本文已影响0人  julieQY7

通过探索Category底层原理回答以下问题

  1. Category是否可以添加方法、属性、成员变量?Category是否可以遵守Protocol?
  2. Category的本质是什么,在底层是怎么存储的?
  3. Category的实现原理是什么,Catagory中的方法是如何调用到的?
  4. Category中是否有Load方法,load方法是什么时候调用的?
  5. load、initialize的区别

Category实现原理(一)中我们通过窥探Category底层结构回答了问题1、2,下面我们继续探究。

Category中的方法调用顺序 - 表象

创建父类MGCPerson,为其添加分类MGCPerson+SportMGCPerson+Eat,创建子类MGCStudent,并添加分配MGCStudent+Study,然后分别添加方法- (void)life+ (void)life

@implementation MGCPerson
- (void)life {
    NSLog(@"MGCPerson : - (void)life");
}

+ (void)life {
    NSLog(@"MGCPerson : + (void)life");
}
@end

@implementation MGCPerson (Sport)
- (void)life {
    NSLog(@"MGCPerson (Sport) : - (void)life");
}

+ (void)life {
    NSLog(@"MGCPerson (Sport) : + (void)life");
}
@end

@implementation MGCPerson (Eat)
- (void)life {
    NSLog(@"MGCPerson (Eat) : - (void)life");
}

+ (void)life {
    NSLog(@"MGCPerson (Eat) : + (void)life");
}
@end
@implementation MGCStudent
- (void)life {
    NSLog(@"MGCStudent : - (void)life");
}

+ (void)life {
    NSLog(@"MGCStudent : + (void)life");
}
@end

@implementation MGCStudent (Study)
- (void)life {
    NSLog(@"MGCStudent (Study) : - (void)life");
}

+ (void)life {
    NSLog(@"MGCStudent (Study) : + (void)life");
}
@end

在main函数中创建MGCStudent类,调用life方法,观察打印

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MGCStudent *student = [[MGCStudent alloc] init];
        [student life];
        [MGCStudent life];
        MGCPerson *person = [[MGCPerson alloc] init];
        [person life];
        [MGCPerson life];
    }
    return 0;
}

// 打印结果
MGCStudent (Study) : - (void)life
MGCStudent (Study) : + (void)life
MGCPerson (Sport) : - (void)life
MGCPerson (Sport) : + (void)life

观察打印结果我们可以得出一个结论:Category中的方法会”覆盖“原类中的方法;仔细观察我们发现MGCPerson相关的调用打印全部来自MGCPerson (Sport)中的方法,但我们明明在MGCPerson的两个分类MGCPerson (Sport)MGCPerson (Eat)中都实现了life方法,为什么优先调用了MGCPerson (Sport)中的life方法?我们猜测这和文件的编译顺序有关,通过在Build Phase -> Compile source中调整文件顺序观察打印结果,我们发现后编译的分类文件优先调用
总结起来就是:

继续研究为什么调用顺序是这样

Category方法调用顺序 - 本质

为什么去runtime中找对应源码?

为什么从void _objc_init(void)开始查找?
简单的说下APP安装到运行的过程:

  1. 从appStore下载app,签名认证通过后装载到手机磁盘中
  2. 点击APP,系统内核部署好APP(进程)运行的初始环境,找到对应APP的可执行文件(Mach-O文件)
    • Mach-O是苹果系统的一种文件格式,app的可执行文件、动态库、静态库最终都是这种文件格式
  3. 根据Mach-O文件找到dyld的路径,内核加载dyld,dyld从系统留下的原始调用栈引导和启动自己
    • dyld(dynamic link editor) 动态链接器,用于链接动态库、资源、app可执行文件格式的Mach-O文件
    • dyld 本身也是一种Mach-O文件
  4. dyld将APP可执行文件(Mach-O文件)加载到内存中,跟据APP可执行文件中的 LoadCommands中的信息去系统共享缓存区(dyld shared cache)将引用的动态库(Mach-O文件)递归加载进内存
  5. 链接包括可执行文件在内APP所需的所有的Mach-O文件
  6. 初始化除可执行文件外的所有Mach-O文件
  7. 初始化APP可执行文件,调用app中的main函数,到这一步就进入我们熟悉的APP入口main函数了

_objc_init就是runtime的初始化函数,也就是在上述第6步时调用的。
在runtime源码中搜索_objc_init

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

可以看到在一系列的初始化后,调用了_dyld_objc_notify_register并且传递了三个函数,去dyld源码中搜索函数_dyld_objc_notify_register的定义

typedef void (*_dyld_objc_notify_mapped)(unsigned count, const char* const paths[], const struct mach_header* const mh[]);
typedef void (*_dyld_objc_notify_init)(const char* path, const struct mach_header* mh);
typedef void (*_dyld_objc_notify_unmapped)(const char* path, const struct mach_header* mh);
// Note: only for use by objc runtime
// Register handlers to be called when objc images are mapped, unmapped, and initialized.
// Dyld will call back the "mapped" function with an array of images that contain an objc-image-info section.
// Those images that are dylibs will have the ref-counts automatically bumped, so objc will no longer need to
// call dlopen() on them to keep them from being unloaded.  During the call to _dyld_objc_notify_register(),
// dyld will call the "mapped" function with already loaded objc images.  During any later dlopen() call,
// dyld will also call the "mapped" function.  Dyld will call the "init" function when dyld would be called
// initializers in that image.  This is when objc calls any +load methods in that image.
void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped);

根据注释在_dyld_objc_notify_register中会调用传入的mapped函数,并传入已映射进内存的objc image(objc对应的模块)

objc image中存储着我们定义的类及分类信息,接下来去runtime源码中查看map_images函数

void
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    rwlock_writer_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}

这个函数实际返回的是map_images_nolock函数的地址,继续查看map_images_nolock函数,map_images_nolock函数中查找了所有包含Objective-Cimage ,然后调用_read_images处理这些image,继续查看_read_images函数
_read_images函数中找到处理分类的代码块

#define EACH_HEADER \
    hIndex = 0;         \
    hIndex < hCount && (hi = hList[hIndex]); \
    hIndex++
    // Discover categories. 
    for (EACH_HEADER) { // 遍历image
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count); // 获取当前image中的分类列表
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();

        for (i = 0; i < count; i++) { // 遍历分类
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls);

            if (!cls) { // 分类对应的原类已经释放了,清空这个分类,打印信息信息,继续下次循环
                // Category's target class is missing (probably weak-linked).
                // Disavow any knowledge of this category.
                catlist[i] = nil;
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class", 
                                 cat->name, cat);
                }
                continue;
            }

            // Process this category. 
            // First, register the category with its target class. 
            // Then, rebuild the class's method lists (etc) if 
            // the class is realized. 
            bool classExists = NO;
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties)  // 分类有方法、协议或属性
            {
                addUnattachedCategoryForClass(cat, cls, hi); // 记录这个分类到一个映射表中
                if (cls->isRealized()) { // 这个类的所有分类已找到
                    remethodizeClass(cls); // 重新整合类中信息
                    classExists = YES;
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category -%s(%s) %s", 
                                 cls->nameForLogging(), cat->name, 
                                 classExists ? "on existing class" : "");
                }
            }

            if (cat->classMethods  ||  cat->protocols  
                ||  (hasClassProperties && cat->_classProperties)) 
            {
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA()->isRealized()) {
                    remethodizeClass(cls->ISA());
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category +%s(%s)", 
                                 cls->nameForLogging(), cat->name);
                }
            }
        }
    }

_read_images函数读取完image中的Category信息后存放起来,然后调用remethodizeClass函数。
remethodizeClass中又调用了附加分类的方法attachCategories,重点来了,下边我们看下attachCategories函数。注释也很重要,注意下述代码中的注释

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));  //malloc一个二维数组存放所有分类的方法列表
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists)); //malloc一个二维数组存放所有分类的属性列表
    protocol_list_t **protolists = (protocol_list_t **)
        malloc(cats->count * sizeof(*protolists)); //malloc一个二维数组存放所有分类的协议列表

    // 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; // 按编译倒序(i--)将各分类的方法列表(mlist) 按正序(mcount++) 放进 二维方法列表(mlists)中
            fromBundle |= entry.hi->isBundle();
        }

        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;// 按编译倒序(i--)将各分类的属性列表(proplist) 按正序(propcount++) 放进 二维属性列表(proplists)中
        }

        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;// 按编译倒序(i--)将各分类的协议列表(protolist) 按正序(protocount++) 放进 二维协议列表(protolists)中
        }
    }

    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);
}

attachCategories按编译倒序将各分类中的方法、协议、属性列表分别整合成一个二维数组后,又各自调用了attachLists方法将整合后的分类信息附加到原类(target class)的cls->data->rw中

    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;
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0])); // 从array()->lists位置开始移动oldCount * sizeof(array()->lists[0])字节到array()->lists + addedCount位置
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0])); // addedLists位置来时,拷贝addedCount * sizeof(array()->lists[0])字节到array()->lists,即将拷贝到array()的前addedCount位置上
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
        } 
        else {
            // 1 list -> many lists
            List* oldList = list;
            uint32_t oldCount = oldList ? 1 : 0;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)malloc(array_t::byteSize(newCount)));
            array()->count = newCount;
            if (oldList) array()->lists[addedCount] = oldList;
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
    }

分析上述代码,可以看到是先将原类中的方法后移,然后再将分类中的方法加入到原类中的,到此我们可以得出以下结论

Category中的+load方法

同样的我们先分别在类及其分类中实现+load+ (void)initialize方法,供后续分析使用

@interface MGCPerson : NSObject
@end
@implementation MGCPerson
+ (void)load {
    NSLog(@"%s", __func__);
}

+ (void)initialize {
    NSLog(@"%s", __func__);
}
@end

@implementation MGCPerson (Sport)
+ (void)load {
    NSLog(@"%s", __func__);
}

+ (void)initialize {
    NSLog(@"%s", __func__);
}
@end

@implementation MGCPerson (Eat)
+ (void)load {
    NSLog(@"%s", __func__);
}

+ (void)initialize {
    NSLog(@"%s", __func__);
}
@end
@interface MGCStudent : MGCPerson
@end
@implementation MGCStudent
+ (void)load {
    NSLog(@"%s", __func__);
}

+ (void)initialize {
    NSLog(@"%s", __func__);
}
@end

@implementation MGCStudent (Study)
+ (void)load {
    NSLog(@"%s", __func__);
}

+ (void)initialize {
    NSLog(@"%s", __func__);
}
@end

@implementation MGCStudent (Holiday)
+ (void)load {
    NSLog(@"%s", __func__);
}

+ (void)initialize {
    NSLog(@"%s", __func__);
}
@end
@interface MGCDog : NSObject
@end
@implementation MGCDog
+ (void)load {
    NSLog(@"%s", __func__);
}

+ (void)initialize {
    NSLog(@"%s", __func__);
}
@end

添加好代码后,不做任何调用和对象创建,直接运行程序,打印如下

+[MGCDog load]
+[MGCPerson load]
+[MGCStudent load]
+[MGCPerson(Eat) load]
+[MGCPerson(Sport) load]
+[MGCStudent(Holiday) load]
+[MGCStudent(Study) load]

可以发现未做任何调用和对象创建的情况下,也会执行+ (void)load方法
尝试在xcode->targets->build phases->compile sources中调整文件编译顺序,发现

既然我们没有调用,我们猜测是runtime启动时调用了load方法,继续查看runtime的入口函数_objc_init

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

从前边对void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped);的分析我们知道第二个参数load_images函数中调用了+load方法,从这里也可以看出load_images中的load不是加载的意思,是load方法的意思

void load_images(const char *path __unused, const struct mach_header *mh)
{
    // Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    {
        rwlock_writer_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh); // 准备工作
    }

    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods(); // 调用load方法
}

load_images中先调用了prepare_load_methods再进一步调用了void call_load_methods(void)
先看下prepare_load_methods做了什么处理,注释很重要,不要忽略它

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])); // 整理类的load方法
    }

    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_loadable_list(cat); // 正序添加到表中
    }
}

分析上述代码可知:类中的load方法优先于Category中的load方法被规划入表中;且类及分类各自按编译正序规划load方法到表中。
继续查看schedule_class_load方法,注释依然很重要,不要忽略它

static void schedule_class_load(Class cls)
{
    if (!cls) return;
    assert(cls->isRealized());  // _read_images should realize

    if (cls->data()->flags & RW_LOADED) return; // 如果已经处理过此类的load方法,则不再处理,所以每个load方法只会调用一次

    // Ensure superclass-first ordering
    schedule_class_load(cls->superclass); // 递归调用schedule_class_load方法,并传入父类

    add_class_to_loadable_list(cls); // 将类中的load方法加入表中
    cls->setInfo(RW_LOADED);  // 置为已处理
}

分析上述代码可知:父类中的load方法优先于子类中的load方法加入表中。
到此准备工作就做完了,接下来看下调用void call_load_methods(void)

void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();

    do {
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) { 
            call_class_loads(); // 调用所有类的load方法,地都用完成后loadable_classes_used置为0
        }

        // 2. Call category +loads ONCE
        more_categories = call_category_loads(); // 调用所有分类的load方法

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}

分析上述代码可知:调用load方法时优先调用类中的load方法,然后再调用分类中的load方法

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方法
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}

分析上述代码可知:类中的load方法按表中正序调用,结合前边load方法的入表顺序可知,先编译的类先调用,且父类优先于子类调用。
查看call_category_loads(void)方法

    struct loadable_category *cats = loadable_categories;
    int used = loadable_categories_used;
    int allocated = loadable_categories_allocated;
    loadable_categories = nil;
    loadable_categories_allocated = 0;
    loadable_categories_used = 0;
    // Call all +loads for the detached list.
    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;

        cls = _category_getClass(cat);
        if (cls  &&  cls->isLoadable()) {
            if (PrintLoading) {
                _objc_inform("LOAD: +[%s(%s) load]\n", 
                             cls->nameForLogging(), 
                             _category_getName(cat));
            }
            (*load_method)(cls, SEL_load); // 调用load方法
            cats[i].cat = nil;
        }
    }

分析上述代码可知:分类中的load方法按表中正序调用,结合前边入表顺序可知,先编译的分类先调用。
综合以上分析,已经验证了本小结开始时的现象分析结论,这里不再赘述

Category中的+ (void)initialize方法

同样先看现象,代码和上一小结(Category中的+load方法)中一样,这里不再列出
上一小结中我们在不添加任何方法调用、对象创建的情况下直接运行程序,发现调用了+load方法,但是并没有调用+ (void)initialize
尝试添加如下代码

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [MGCStudent class];
        [[MGCDog alloc] init];
        [[MGCDog alloc] init];
        [[MGCDog alloc] init];
        [[MGCDog alloc] init];
    }
    return 0;
}

打印结果

+[MGCPerson(Eat) initialize]
+[MGCStudent(Study) initialize]
+[MGCDog initialize]

可以看到在我们没有调用+ (void)initialize的情况下,+ (void)initialize方法依然被调用了,且无论调用的是类类方法还是实例方法,+ (void)initialize都会被调用并且只会调用一次。
因此我们猜测是类第一次接收到消息时调用了+ (void)initialize方法。即第一次接收objc_msgSend消息时调用了+ (void)initialize方法。
尝试去runtime中查找objc_msgSend方法,这个方法的源码是汇编的。
最终调用到了void _class_initialize(Class cls)方法

void _class_initialize(Class cls) {
    Class supercls;
    bool reallyInitialize = NO;

    // Make sure super is done initializing BEFORE beginning to initialize cls.
    // See note about deadlock above.
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        _class_initialize(supercls); // 递归调用_class_initialize方法,并传入父类指针
    }
    
    // Try to atomically set CLS_INITIALIZING.
    {
        monitor_locker_t lock(classInitLock);
        if (!cls->isInitialized() && !cls->isInitializing()) { // 如果类没有调用Initialize方法,则准备调用
            cls->setInitializing();
            reallyInitialize = YES;
        }
    }

    if (reallyInitialize) {
          callInitialize(cls); // 调用当前类的callInitialize方法
    }
}

分析上述代码可知:在调用自己的callInitialize之前会先调用父类的callInitialize

void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    asm("");
}

分析上述代码可知:Initialize最终是通过objc_msgSend调用的,既然最终是通过objc_msgSend调用的,那就遵从objc_msgSend方法调用的原理。即分类方法会"覆盖"原类的中的方法。
综合以上可知:

上一篇下一篇

猜你喜欢

热点阅读