美文网首页
深入分析 Category

深入分析 Category

作者: mtry | 来源:发表于2020-03-17 16:34 被阅读0次

    简介

    • Category 主要作用
    • Category 与 Class Extension
    • Category 加载时机
    • 总结

    1、Category主要作用

    Category是Objective-C 2.0之后添加的语言特性,Category的主要作用是为已经存在的类添加方法。苹果还推荐以下方式。

    • 把类的实现分开在几个不同的文件里面。主要作用:

      • 可以减少单个文件的体积
      • 可以把不同的功能组织到不同的category里
      • 可以由多个开发者共同完成一个类
      • 可以按需加载想要的category 等等。
    • 声明私有方法

    不过有两个设计原则必须要遵守:

    1. Category 的实现可以依赖主类,但主类一定不依赖 Category,也就是说移除任何一个 Category 的代码不会对主类产生任何影响。
    2. Category 可以直接使用主类已有的私有成员变量,但不应该为实现 Category 而往主类中添加成员变量,考虑在 Category 的实现中使用 objc association 来达到相同效果。

    2、Category 与 Class Extension

    Class Extension 看起来很像一个匿名的 Category,但是 Class Extension 和有名字的 Category 几乎完全是两个东西。Class Extension 和 Category 在语言机制上有着很大差别:Class Extension 在编译期就会将定义的 Ivar、属性、方法等直接合入主类,而 Category 在程序启动 Runtime Loading 时才会将属性(没 Ivar)和方法合入主类。

    我们知道 Category 可以有 N 个,其实 Class Extension 也可以有,且它不限于写在 .m 中,只要在 @implementation 前定义就可以,我们可以利用这个性质,将 Header 中的声明按功能归类。

    比如:

    @interface Person : NSObject
    
    @property (nonatomic, strong) NSString *aaaa_name;
    
    - (void)aaaa_run;
    
    @end
    
    #import "Person.h"
    #import "PersonHeader1.h"
    #import "PersonHeader2.h"
    
    @implementation Person
    
    - (void)aaaa_run
    {
        NSLog(@"aaaa_run");
    }
    
    @end
    
    //PersonHeader1.h
    @interface Person ()
    
    @property (nonatomic, assign) NSInteger aaaa_age;
    
    @end
    
    //PersonHeader2.h
    @interface Person ()
    
    @property (nonatomic, strong) NSString *aaaa_IdCard;
    
    @end
    

    我们可以根据需要,通过 import 不同的头文件来使用 aaaa_age 或者 aaaa_IdCard;我们也可以在接口封装的时候对外只暴露 aaaa_name 属性。

    最后,我们来验证 Class Extension 的加入时机,我们可以使用 clang 查看编译文件

    clang -rewrite-objc Person.m
    

    直接看 Person.cpp 最后一段

    
    extern "C" unsigned long OBJC_IVAR_$_Person$_aaaa_name;
    extern "C" unsigned long OBJC_IVAR_$_Person$_aaaa_IdCard;
    extern "C" unsigned long OBJC_IVAR_$_Person$_aaaa_age;
    struct Person_IMPL {
        struct NSObject_IMPL NSObject_IVARS;
        NSString * _Nonnull _aaaa_name;
        NSString *_aaaa_IdCard;
        NSInteger _aaaa_age;
    };
    
    
    // @property (nonatomic, strong) NSString *aaaa_name;
    
    // - (void)aaaa_run;
    
    /* @end */
    
    
    // @interface Person ()
    
    // @property (nonatomic, assign) NSInteger aaaa_age;
    
    /* @end */
    
    // @interface Person ()
    
    // @property (nonatomic, strong) NSString *aaaa_IdCard;
    
    /* @end */
    
    
    // @implementation Person
    
    
    static void _I_Person_aaaa_run(Person * self, SEL _cmd) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_47_6fxgzrp50fv6r72895k282k40000gn_T_Person_933130_mi_0);
    }
    
    
    static NSString * _Nonnull _I_Person_aaaa_name(Person * self, SEL _cmd) { return (*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_Person$_aaaa_name)); }
    static void _I_Person_setAaaa_name_(Person * self, SEL _cmd, NSString * _Nonnull aaaa_name) { (*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_Person$_aaaa_name)) = aaaa_name; }
    
    static NSString * _I_Person_aaaa_IdCard(Person * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_aaaa_IdCard)); }
    static void _I_Person_setAaaa_IdCard_(Person * self, SEL _cmd, NSString *aaaa_IdCard) { (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_aaaa_IdCard)) = aaaa_IdCard; }
    
    static NSInteger _I_Person_aaaa_age(Person * self, SEL _cmd) { return (*(NSInteger *)((char *)self + OBJC_IVAR_$_Person$_aaaa_age)); }
    static void _I_Person_setAaaa_age_(Person * self, SEL _cmd, NSInteger aaaa_age) { (*(NSInteger *)((char *)self + OBJC_IVAR_$_Person$_aaaa_age)) = aaaa_age; }
    // @end
    
    struct _prop_t {
        const char *name;
        const char *attributes;
    };
    
    struct _protocol_t;
    
    struct _objc_method {
        struct objc_selector * _cmd;
        const char *method_type;
        void  *_imp;
    };
    
    struct _protocol_t {
        void * isa;  // NULL
        const char *protocol_name;
        const struct _protocol_list_t * protocol_list; // super protocols
        const struct method_list_t *instance_methods;
        const struct method_list_t *class_methods;
        const struct method_list_t *optionalInstanceMethods;
        const struct method_list_t *optionalClassMethods;
        const struct _prop_list_t * properties;
        const unsigned int size;  // sizeof(struct _protocol_t)
        const unsigned int flags;  // = 0
        const char ** extendedMethodTypes;
    };
    
    struct _ivar_t {
        unsigned long int *offset;  // pointer to ivar offset location
        const char *name;
        const char *type;
        unsigned int alignment;
        unsigned int  size;
    };
    
    struct _class_ro_t {
        unsigned int flags;
        unsigned int instanceStart;
        unsigned int instanceSize;
        unsigned int reserved;
        const unsigned char *ivarLayout;
        const char *name;
        const struct _method_list_t *baseMethods;
        const struct _objc_protocol_list *baseProtocols;
        const struct _ivar_list_t *ivars;
        const unsigned char *weakIvarLayout;
        const struct _prop_list_t *properties;
    };
    
    struct _class_t {
        struct _class_t *isa;
        struct _class_t *superclass;
        void *cache;
        void *vtable;
        struct _class_ro_t *ro;
    };
    
    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;
    };
    extern "C" __declspec(dllimport) struct objc_cache _objc_empty_cache;
    #pragma warning(disable:4273)
    
    extern "C" unsigned long int OBJC_IVAR_$_Person$_aaaa_name __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct Person, _aaaa_name);
    extern "C" unsigned long int OBJC_IVAR_$_Person$_aaaa_IdCard __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct Person, _aaaa_IdCard);
    extern "C" unsigned long int OBJC_IVAR_$_Person$_aaaa_age __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct Person, _aaaa_age);
    
    static struct /*_ivar_list_t*/ {
        unsigned int entsize;  // sizeof(struct _prop_t)
        unsigned int count;
        struct _ivar_t ivar_list[3];
    } _OBJC_$_INSTANCE_VARIABLES_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_ivar_t),
        3,
        {{(unsigned long int *)&OBJC_IVAR_$_Person$_aaaa_name, "_aaaa_name", "@\"NSString\"", 3, 8},
         {(unsigned long int *)&OBJC_IVAR_$_Person$_aaaa_IdCard, "_aaaa_IdCard", "@\"NSString\"", 3, 8},
         {(unsigned long int *)&OBJC_IVAR_$_Person$_aaaa_age, "_aaaa_age", "q", 3, 8}}
    };
    
    static struct /*_method_list_t*/ {
        unsigned int entsize;  // sizeof(struct _objc_method)
        unsigned int method_count;
        struct _objc_method method_list[7];
    } _OBJC_$_INSTANCE_METHODS_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        7,
        {{(struct objc_selector *)"aaaa_run", "v16@0:8", (void *)_I_Person_aaaa_run},
        {(struct objc_selector *)"aaaa_name", "@16@0:8", (void *)_I_Person_aaaa_name},
        {(struct objc_selector *)"setAaaa_name:", "v24@0:8@16", (void *)_I_Person_setAaaa_name_},
        {(struct objc_selector *)"aaaa_IdCard", "@16@0:8", (void *)_I_Person_aaaa_IdCard},
        {(struct objc_selector *)"setAaaa_IdCard:", "v24@0:8@16", (void *)_I_Person_setAaaa_IdCard_},
        {(struct objc_selector *)"aaaa_age", "q16@0:8", (void *)_I_Person_aaaa_age},
        {(struct objc_selector *)"setAaaa_age:", "v24@0:8q16", (void *)_I_Person_setAaaa_age_}}
    };
    
    static struct /*_prop_list_t*/ {
        unsigned int entsize;  // sizeof(struct _prop_t)
        unsigned int count_of_properties;
        struct _prop_t prop_list[1];
    } _OBJC_$_PROP_LIST_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_prop_t),
        1,
        {{"aaaa_name","T@\"NSString\",&,N,V_aaaa_name"}}
    };
    
    static struct _class_ro_t _OBJC_METACLASS_RO_$_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        1, sizeof(struct _class_t), sizeof(struct _class_t), 
        (unsigned int)0, 
        0, 
        "Person",
        0, 
        0, 
        0, 
        0, 
        0, 
    };
    
    static struct _class_ro_t _OBJC_CLASS_RO_$_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        0, __OFFSETOFIVAR__(struct Person, _aaaa_name), sizeof(struct Person_IMPL), 
        (unsigned int)0, 
        0, 
        "Person",
        (const struct _method_list_t *)&_OBJC_$_INSTANCE_METHODS_Person,
        0, 
        (const struct _ivar_list_t *)&_OBJC_$_INSTANCE_VARIABLES_Person,
        0, 
        (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person,
    };
    
    extern "C" __declspec(dllimport) struct _class_t OBJC_METACLASS_$_NSObject;
    
    extern "C" __declspec(dllexport) struct _class_t OBJC_METACLASS_$_Person __attribute__ ((used, section ("__DATA,__objc_data"))) = {
        0, // &OBJC_METACLASS_$_NSObject,
        0, // &OBJC_METACLASS_$_NSObject,
        0, // (void *)&_objc_empty_cache,
        0, // unused, was (void *)&_objc_empty_vtable,
        &_OBJC_METACLASS_RO_$_Person,
    };
    
    extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_NSObject;
    
    extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_Person __attribute__ ((used, section ("__DATA,__objc_data"))) = {
        0, // &OBJC_METACLASS_$_Person,
        0, // &OBJC_CLASS_$_NSObject,
        0, // (void *)&_objc_empty_cache,
        0, // unused, was (void *)&_objc_empty_vtable,
        &_OBJC_CLASS_RO_$_Person,
    };
    static void OBJC_CLASS_SETUP_$_Person(void ) {
        OBJC_METACLASS_$_Person.isa = &OBJC_METACLASS_$_NSObject;
        OBJC_METACLASS_$_Person.superclass = &OBJC_METACLASS_$_NSObject;
        OBJC_METACLASS_$_Person.cache = &_objc_empty_cache;
        OBJC_CLASS_$_Person.isa = &OBJC_METACLASS_$_Person;
        OBJC_CLASS_$_Person.superclass = &OBJC_CLASS_$_NSObject;
        OBJC_CLASS_$_Person.cache = &_objc_empty_cache;
    }
    #pragma section(".objc_inithooks$B", long, read, write)
    __declspec(allocate(".objc_inithooks$B")) static void *OBJC_CLASS_SETUP[] = {
        (void *)&OBJC_CLASS_SETUP_$_Person,
    };
    static struct _class_t *L_OBJC_LABEL_CLASS_$ [1] __attribute__((used, section ("__DATA, __objc_classlist,regular,no_dead_strip")))= {
        &OBJC_CLASS_$_Person,
    };
    static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
    
    

    我们可以看到属性 aaaa_name, aaaa_age, aaaa_IdCard编译完也就绑定完了。

    3、Category 加载时机

    先说结论:Category 都是在运行加载到对应类中,不过要分懒加载和非懒加载的方式,非懒加载主要是系统类对应的分类,懒加载则主要是自定义类的分类。

    非懒加载分类

    比如下面分类

    //NSObject+SystemTest.h
    @interface NSObject (SystemTest)
    
    - (void)ssss_fun;
    
    @end
    
    //NSObject+SystemTest.m
    @implementation NSObject (SystemTest)
    
    - (void)ssss_fun
    {
        NSLog(@"ssss_done");
    }
    
    @end
    
    

    clang -rewrite-objc NSObject+SystemTest.m 截取最关键部分代码如下

    struct _category_t {
        const char *name;                                // 类名
        struct _class_t *cls;                            // 类,在运行时阶段通过 clasee_name(类名)对应到类对象
        const struct _method_list_t *instance_methods;   // Category 中所有添加的对象方法列表
        const struct _method_list_t *class_methods;      // Category 中所有添加的类方法列表
        const struct _protocol_list_t *protocols;        // Category 中实现的所有协议列表
        const struct _prop_list_t *properties;           // Category 中添加的所有属性
    };
    extern "C" __declspec(dllimport) struct objc_cache _objc_empty_cache;
    #pragma warning(disable:4273)
    
    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_NSObject_$_SystemTest __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        1,
        {{(struct objc_selector *)"ssss_fun", "v16@0:8", (void *)_I_NSObject_SystemTest_ssss_fun}}
    };
    
    extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_NSObject;
    
    static struct _category_t _OBJC_$_CATEGORY_NSObject_$_SystemTest __attribute__ ((used, section ("__DATA,__objc_const"))) = 
    {
        "NSObject",
        0, // &OBJC_CLASS_$_NSObject,
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_NSObject_$_SystemTest,
        0,
        0,
        0,
    };
    static void OBJC_CATEGORY_SETUP_$_NSObject_$_SystemTest(void ) {
        _OBJC_$_CATEGORY_NSObject_$_SystemTest.cls = &OBJC_CLASS_$_NSObject;
    }
    #pragma section(".objc_inithooks$B", long, read, write)
    __declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = {
        (void *)&OBJC_CATEGORY_SETUP_$_NSObject_$_SystemTest,
    };
    static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
        &_OBJC_$_CATEGORY_NSObject_$_SystemTest,
    };
    static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
    

    由于 NSObject+SystemTest.m 只添加了 - (void)ssss_fun 方法,所以在编译的时候只生成了 _method_list_t 结构体的实例 _OBJC_$_CATEGORY_INSTANCE_METHODS_NSObject_$_SystemTest ,同时也生产了 _category_t 结构体的实例 _OBJC_$_CATEGORY_NSObject_$_SystemTest 为后面 Runtime 的加载做准备。

    接下来我们看 Runtime 中的加载过程,我看源码的是 objc4-750.1

    通过在 objc-runtime-new.mm 中的 prepareMethodLists 函数中进行断点来观察加载堆栈。

    * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
      * frame #0: libobjc.A.dylib`prepareMethodLists(cls=NSObject, addedLists=0x0000000101804440, addedCount=1, baseMethods=false, methodsFromBundle=false) at objc-runtime-new.mm:749:35
        frame #1: libobjc.A.dylib`attachCategories(cls=NSObject, cats=0x0000000101804760, flush_caches=true) at objc-runtime-new.mm:816:5
        frame #2: libobjc.A.dylib`remethodizeClass(cls=NSObject) at objc-runtime-new.mm:930:9
        frame #3: libobjc.A.dylib`::_read_images(hList=0x00007ffeefbf3890, hCount=259, totalClasses=21723, unoptimizedTotalClasses=21723) at objc-runtime-new.mm:2766:21
        frame #4: libobjc.A.dylib`::map_images_nolock(mhCount=259, mhPaths=0x00007ffeefbf4f80, mhdrs=0x00007ffeefbf41f0) at objc-os.mm:577:9
        frame #5: libobjc.A.dylib`::map_images(count=259, paths=0x00007ffeefbf4f80, mhdrs=0x00007ffeefbf41f0) at objc-runtime-new.mm:2193:12
        frame #6: dyld`dyld::notifyBatchPartial(dyld_image_states, bool, char const* (*)(dyld_image_states, unsigned int, dyld_image_info const*), bool, bool) + 1767
        frame #7: dyld`dyld::registerObjCNotifiers(void (*)(unsigned int, char const* const*, mach_header const* const*), void (*)(char const*, mach_header const*), void (*)(char const*, mach_header const*)) + 63
        frame #8: libdyld.dylib`_dyld_objc_notify_register + 113
        frame #9: libobjc.A.dylib`::_objc_init() at objc-os.mm:889:5
        frame #10: libdispatch.dylib`_os_object_init + 13
        frame #11: libdispatch.dylib`libdispatch_init + 282
        frame #12: libSystem.B.dylib`libSystem_initializer + 220
        frame #13: dyld`ImageLoaderMachO::doModInitFunctions(ImageLoader::LinkContext const&) + 539
        frame #14: dyld`ImageLoaderMachO::doInitialization(ImageLoader::LinkContext const&) + 40
        frame #15: dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 493
        frame #16: dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 344
        frame #17: dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 188
        frame #18: dyld`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 82
        frame #19: dyld`dyld::initializeMainExecutable() + 129
        frame #20: dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 6797
        frame #21: dyld`dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*) + 453
        frame #22: dyld`_dyld_start + 37
    

    我们把关键路径标出来如下

    frame #0: libobjc.A.dylib`prepareMethodLists() at objc-runtime-new.mm:749:35
    frame #1: libobjc.A.dylib`attachCategories() at objc-runtime-new.mm:816:5
    frame #2: libobjc.A.dylib`remethodizeClass() at objc-runtime-new.mm:930:9
    frame #3: libobjc.A.dylib`::_read_images() at objc-runtime-new.mm:2766:21
    frame #4: libobjc.A.dylib`::map_images_nolock() at objc-os.mm:577:9
    frame #5: libobjc.A.dylib`::map_images() at objc-runtime-new.mm:2193:12
    frame #9: libobjc.A.dylib`::_objc_init() at objc-os.mm:889:5
    

    _objc_initruntime 的初始化方法入口,同时向 dyld 注册每当有新的 images 加载之后,回调 map_images() 函数。

    void _objc_init(void)
    {
        //...
        _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    }
    
    void 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);
    }
    
    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);
        }
    }
    
    
    

    下面我们从 _read_images 这个方法开始分析,在这个方法里加载了所有的类、协议和 Category,其中下面是关于分类的加载逻辑。

    void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) {
        ...
        
        for (EACH_HEADER) {
            //获取全部的分类列表
            category_t **catlist = 
                _getObjc2CategoryList(hi, &count);
            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;
                    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) 
                {
                    //把cat分类跟cls类进行绑定
                    addUnattachedCategoryForClass(cat, cls, hi);
                    if (cls->isRealized()) {
                        //把分类的方法、属性、协议添加到类上
                        remethodizeClass(cls);
                        classExists = YES;
                    }
                }
    
                if (cat->classMethods  ||  cat->protocols  
                    ||  (hasClassProperties && cat->_classProperties)) 
                {
                    addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                    if (cls->ISA()->isRealized()) {
                        remethodizeClass(cls->ISA());
                    }
                }
            }
        }
        ...
    }
    
    static void remethodizeClass(Class cls) {
        category_list *cats;
        bool isMeta;
    
        runtimeLock.assertLocked();
    
        isMeta = cls->isMetaClass();
    
        // Re-methodizing: check for more categories
        if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
            attachCategories(cls, cats, true /*flush caches*/);        
            free(cats);
        }
    }
    

    attachCategories 合并全部category中的属性、协议、方法,通过attachLists添加到类中

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

    attachListsaddedLists,插在 array() 前面,这就是为什么如果分类跟类有相同函数的时候,往往回有分类中的函数会“覆盖”了类中的函数。其实不是覆盖了,只是分类的函数排到前面了。

    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]));
            memcpy(array()->lists, addedLists,
                   addedCount * sizeof(array()->lists[0]));
        }
        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]));
        }
    }
    

    至此可以得出一个结论了,系统类的分类是在 rumtime 初始化的时候就开始加载了,也就是在 main() 函数之前就开始加载了。

    懒加载分类

    同样先说结论,自定义类是在当前类初次调用的时候,类进行初始化,所以分类中的方法、属性等也是这个时候合并到对应类中,也就是 main() 函数之后就开始加载了。

    @interface Person : NSObject
    
    - (void)pppp_run;
    
    @end
    
    @implementation Person
    
    - (void)pppp_run
    {
        NSLog(@"pppp_run");
    }
    
    @end
    
    @interface Person (CustomizeTest)
    
    - (void)cccc_work;
    
    @end
    
    @implementation Person (CustomizeTest)
    
    - (void)cccc_work
    {
        NSLog(@"cccc_work");
    }
    
    @end
    
    

    main 中的测试代码

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            [[Person new] cccc_work];
        }
        return 0;
    }
    

    还是通过在 objc-runtime-new.mm 中的 prepareMethodLists 函数中进行断点来观察加载堆栈。

    * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
      * frame #0: libobjc.A.dylib`prepareMethodLists(cls=Person, addedLists=0x00007ffeefbff238, addedCount=1, baseMethods=true, methodsFromBundle=false) at objc-runtime-new.mm:752:37
        frame #1: libobjc.A.dylib`methodizeClass(cls=Person) at objc-runtime-new.mm:857:9
        frame #2: libobjc.A.dylib`realizeClass(cls=Person) at objc-runtime-new.mm:2005:5
        frame #3: libobjc.A.dylib`getNonMetaClass(metacls=0x00000001000011b8, inst=0x00000001000011e0) at objc-runtime-new.mm:1443:9
        frame #4: libobjc.A.dylib`::_class_getNonMetaClass(cls=0x00000001000011b8, obj=0x00000001000011e0) at objc-runtime-new.mm:1536:11
        frame #5: libobjc.A.dylib`::lookUpImpOrForward(cls=0x00000001000011b8, sel="new", inst=0x00000001000011e0, initialize=true, cache=false, resolver=true) at objc-runtime-new.mm:4926:28
        frame #6: libobjc.A.dylib`::_class_lookupMethodAndLoadCache3(obj=0x00000001000011e0, sel="new", cls=0x00000001000011b8) at objc-runtime-new.mm:4877:12
        frame #7: libobjc.A.dylib`_objc_msgSend_uncached at objc-msg-x86_64.s:1073
        frame #8: runtimeTest`main(argc=1, argv=0x00007ffeefbff590) at main.m:17:10
        frame #9: libdyld.dylib`start + 1
    
    

    提取关键信息之后

    frame #0: libobjc.A.dylib`prepareMethodLists() at objc-runtime-new.mm:752:37
    frame #1: libobjc.A.dylib`methodizeClass() at objc-runtime-new.mm:857:9
    frame #2: libobjc.A.dylib`realizeClass() at objc-runtime-new.mm:2005:5
    frame #3: libobjc.A.dylib`getNonMetaClass() at objc-runtime-new.mm:1443:9
    frame #4: libobjc.A.dylib`::_class_getNonMetaClass() at objc-runtime-new.mm:1536:11
    frame #5: libobjc.A.dylib`::lookUpImpOrForward() at objc-runtime-new.mm:4926:28
    frame #6: libobjc.A.dylib`::_class_lookupMethodAndLoadCache3() at objc-runtime-new.mm:4877:12
    frame #7: libobjc.A.dylib`_objc_msgSend_uncached at objc-msg-x86_64.s:1073
    frame #8: runtimeTest`main() at main.m:17:10
    

    _objc_msgSend_uncached_class_lookupMethodAndLoadCache3()lookUpImpOrForward()_class_getNonMetaClass()getNonMetaClass() 这几个方法主要是寻找要调用的方法,如果没有找到并且当前类还没初始化,然后开始调用 realizeClass() 开始初始化,我们重点关注 methodizeClass() 函数的实现。

    // 将类实现的方法(包括分类)、属性和遵循的协议添加到class_rw_t结构体中的methods、properties、protocols列表中
    static void methodizeClass(Class cls)
    {
        runtimeLock.assertLocked();
    
        bool isMeta = cls->isMetaClass();
        auto rw = cls->data();
        auto ro = rw->ro;
    
        // Install methods and properties that the class implements itself.
        // 将class_ro_t中的methodList添加到class_rw_t结构体中的methodList
        method_list_t *list = ro->baseMethods();
        if (list) {
            prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
            rw->methods.attachLists(&list, 1);
        }
    
        // 将class_ro_t中的propertyList添加到class_rw_t结构体中的propertyList
        property_list_t *proplist = ro->baseProperties;
        if (proplist) {
            rw->properties.attachLists(&proplist, 1);
        }
    
        // 将class_ro_t中的protocolList添加到class_rw_t结构体中的protocolList
        protocol_list_t *protolist = ro->baseProtocols;
        if (protolist) {
            rw->protocols.attachLists(&protolist, 1);
        }
    
        // Root classes get bonus method implementations if they don't have 
        // them already. These apply before category replacements.
        if (cls->isRootMetaclass()) {
            // root metaclass
            addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO);
        }
    
        // Attach categories.
        // 添加category方法
        category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
        attachCategories(cls, cats, false /*don't flush caches*/);
        
        if (cats) free(cats);
    }
    

    根据断点调试我们是在 ro->baseMethods() 中发现分类方法 cccc_work

    (lldb) p list
    (method_list_t *) $0 = 0x0000000100001110
    (lldb) p $0.get(0)
    (method_t) $1 = {
      name = "cccc_work"
      types = 0x0000000100000fab "v16@0:8"
      imp = 0x0000000100000e10 (runtimeTest`-[Person(CustomizeTest) cccc_work] at Person+CustomizeTest.m:15)
    }
    (lldb) p $0.get(1)
    (method_t) $2 = {
      name = "pppp_run"
      types = 0x0000000100000fab "v16@0:8"
      imp = 0x0000000100000e70 (runtimeTest`-[Person pppp_run] at Person.m:12)
    }
    

    我们可以看到分类的实例方法跟类的实例方法这个这里就同时合并到类中去了,虽然最后 attachCategories 也有这个方法,不过 cats 一直为空,跟系统类的分类还是有很大的差异的。

    4、总结

    • 分类跟扩展本质是不同的,扩展在是类的组成部分在编译期就决定了,分类是在运行期才把属性、方法、协议合入类中。
    • 扩展支持多个,只要在 @implementation 前定义就可以。
    • 为系统类添加的分类跟自定义类添加分类加载方式是不一样的,不过效果是一样的。
    • 如果分类跟类的方法重复,那么会优先调用分类中的方法。
    • 如果分类之间也有方法重复,那么就是根据加载顺序决定,后加载的先调用。

    5、参考资料

    相关文章

      网友评论

          本文标题:深入分析 Category

          本文链接:https://www.haomeiwen.com/subject/ytntyhtx.html