美文网首页
iOS 分类原理探究

iOS 分类原理探究

作者: shen888 | 来源:发表于2019-12-18 17:08 被阅读0次

    一、分类的定义

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

    如上面代码显示,分类的本质是一个结构体,它可以存储类的实例方法(instanceMethods)、类方法(classMethods)、协议(protocols)、实例属性(instanceProperties)和类属性(_classProperties)。值得注意的是分类中没有存储成员变量的元素。

    二、分类的特点和作用(通过和扩展比较来确定)

    分类的特点:
    1. 分类在运行时决议
    2. 可以为系统类添加分类
    3. 分类可以添加实例方法、类方法、协议、属性。添加属性时只声明 set、get 方法,没有对应实现,不会添加成员变量
    扩展的特点:
    1. 扩展在编译时决议
    2. 不可以为系统类添加扩展
    3. 扩展可以声明属性、方法(一般不声明方法),大多数寄生在宿主类的 .m 文件中,如果添加属性,在编译时通过 @synthesize (xcode 4.5 后可省略)自动为属性实现 get、set 方法,并添加成员变量,声明方法则需要在宿主类中去实现。
    分类中的 +load 和 +initialize 方法
    1. +load 调用时机:是 runtime 的加载类,在程序启动装载类信息的时候调用,通过函数地址直接调用,每个 +load 都会调用且仅调用一次。
    2. +load 调用顺序和方式:顺序:父类 +load --> 子类 +load --> 分类 +load;类中的 + load 优先于分类中的。每个分类中的和原类中的 +load 方法都会被调用,不会互相覆盖。
    3. +initialize 调用时机:+initialize 是通过 objc_msgSend 调用,只在每个类只在第一次初始化时候调用一次,如果子类没有实现 +initialize 会去调用父类的 +initialize。分类中的 + initialize 会覆盖原类的实现。
    4. +initialize 调用顺序和方式: 在某个类第一次初始化时,会先强制为初始化过的父类先调用 +initialize,然后本类再调用 +initialize。

    例如: B、C 类都继承 A,且三个类都实现了 +initialize,在依次初始化 B、C 的实例对象时,初始化 B 类时会依次调用A、B 的 +initialize,初始化 C 类时只会调用 C 类的 +initialize,因为 A 的 +initialize 已经在初始化 B 的时候调用了。但如果 B 类没有实现 +initialize,那么在初始化 B 类的时候会调用两次 A 的 +initialize。

    分类的其他特点:
    1. 分类添加的方法可以“覆盖”原类的方法,同名分类方法谁能生效取决于编译顺序——最后被编译的分类,会优先被生效。
    2. 可以使用关联对象的技术模拟为分类添加成员变量,其本质与原类中的成员变量并不相同,只是实现了类似的功能。
    分类的作用
    1. 声明私有方法
    2. 分解体积庞大的类文件
    3. 把 framework 的私有方法公开化

    下面我们探究一下分类的这些特点,我会以 特点1、特点2…… 这些来代表上面分类的特点。

    三、分类原理探究(重点)

    为了探究分类的原理,需要下载Runtime的源码,官方的工程需要经过大量调试才能使用。这里有处理好的objc4-756.2工程,以下都是基于处理好的objc4-756.2工程说明的。

    将分类添加到宿主类中的过程

    我们运行 objc-debug TARGET,看看系统如何加载分类的。
    首先是 runtime 的初始化函数 _objc_init(void) 函数

    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 中的 &map_images,点进去后

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

    再看 map_images_nolock

    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);
        }
    
        firstTime = NO;
    }
    

    我们进入 _read_images

    void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
    {
       ..........
        前面还有一堆代码,我们先不关心
       ..........
        // Discover categories. 
        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;
                    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);
                    }
                }
            }
        }
       ..........
        后面还有一堆代码,我们先不关心
       ..........
    }
    

    我们可以看到 // Discover categories. 的注释,告诉我们这里是关于处理分类的。

    最外层的 for 循环判断条件 EACH_HEADER,实际上是#define EACH_HEADER \ hIndex = 0; \ hIndex < hCount && (hi = hList[hIndex]); \ hIndex++ 的宏定义,就是遍历 hList 取出 hi。

    hi 中包含各种资源信息,其中就包括分类的信息。
    category_t **catlist = _getObjc2CategoryList(hi, &count); 这句代码就是获取 hi 中的分类的信息,返回值是一个 category_t 的数组,category_t 结构体我们在一开始就有了了解

    循环里面的逻辑就是:拿到 category_t 类型的 cat,if (cat->instanceMethods || cat->protocols || cat->instanceProperties)if (cat->classMethods || cat->protocols || (hasClassProperties && cat->_classProperties))是对类和其元类是否需要处理分类的判断。处理的具体逻辑都在 remethodizeClass 函数中。

    这里我们注意一下 addUnattachedCategoryForClass 这个函数,从名字上的意思就是“为类添加未命中的分类”,这里的 cat 就是一个未命中的分类,在之后的 remethodizeClass 函数中就是去处理未命中的分类的。

    image.png

    我在这里打个断点,运行后第一次进入会出现上图信息,会处理 NSObject 及其元类的分类,具体处理逻辑请看 remethodizeClass 函数。

    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*/))) {
            if (PrintConnecting) {
                _objc_inform("CLASS: attaching categories to class '%s' %s", 
                             cls->nameForLogging(), isMeta ? "(meta)" : "");
            }
            
            attachCategories(cls, cats, true /*flush caches*/);        
            free(cats);
        }
    }
    

    remethodizeClass 函数里面使用了 unattachedCategoriesForClass 函数,和上面的addUnattachedCategoryForClass 对应,上面是添加,这里是获取。获取到“未命中的分类”后调用了 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));
        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);
    }
    

    这里面的 cats 是上个函数传过来的“未命中的分类”,显然“未命中的分类”可能不止一个,故而这是一个数组

    下面定义的 mlists 、proplists、protolists是类的方法列表、属性列表和协议列表。它们都是一个二维数组,对应每一个“未命中分类”的方法列表、属性列表和协议列表,以 mlists 举例,其数据类型是这样的 [[method_t, method_t], [method_t, method_t, method_t], [method_t, method_t] ...]。因为类方法和实例方法会处理两次,所有这里如果是普通类,存储的是实例方法、属性,元类的话会存储类方法和属性。

    while (i--) {...} 循环意味着倒序变量,也就意味着最先访问的是最后编译的分类。这也解释了 特点 8 的原理。这个寻做的事情就是把每个“未命中分类”中的方法、属性、协议倒序放入mlists 、proplists、protolists中。

    后面的代码就是将 mlists 、proplists、protolists 附加到 rw 对应元素的里面。auto rw = cls->data(); rw 是类的可读写数据,意味着系统最终将分类里面的元素全部添加到宿主类中了,这也是为什么分类可以为宿主类添加实例方法、类方法、协议、属性的原因。

    系统通过 objc_msgSend 函数来调用类的方法,由上面可以知道,系统能够找到分类的方法,顺序是最后添加的先被找到,而分类方法和原类中的方法谁先调用,这个函数中看不出来,所有我们需要再看看 attachLists 函数

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

    我们注意看 hasArray() 为 YES 的分支,因为这个我们可以知道分类中的元素究竟是添加在数组的前面,还是后面,这里有两个核心函数 memmove 和 memcpy,所有我们看看它俩究竟干了什么。

    // memmove :内存移动。
    /*  __dst : 移动内存的目的地
    *   __src : 被移动的内存首地址
    *   __len : 被移动的内存长度
    *   将__src的内存移动__len块内存到__dst中
    */
    void    *memmove(void *__dst, const void *__src, size_t __len);
    

    __dst : 移动内存的目的地:对应的是 array()->lists + addedCount 很明显目的地是 array 的首地址加上被添加数组的大小,也就是要把数组前面的地方给留出来,这里我们就差不多知道新元素应该是要添加到数组前面了。

    为了了解全貌,我们继续看 __src : 被移动的内存首地址:对应 array()->lists 就是数组的首地址。__len : 被移动的内存长度:对应 addedCount * sizeof(array()->lists[0])就是数组中每个元素的长度。所有 memmove 就是将原来数组的元素向后移动 addedCount 个位置。那么很显然 memcpy 就是将预留出来的地方填满数据。

    // memcpy :内存拷贝。
    /*  __dst : 拷贝内存的拷贝目的地
    *   __src : 被拷贝的内存首地址
    *   __n : 被移动的内存长度
    *   将__src的内存拷贝__n块内存到__dst中
    */
    void    *memcpy(void *__dst, const void *__src, size_t __n);
    

    至此我们将 特点 8 完全讲述清楚了。我们看到分类是在运行后才加到宿主类中的,这也解释了 特点 1,也正是因为 特点 1,我们才可以为系统类添加分类(特点 2),通过分类结构体 category_t 已经刚才的流程说明了 特点 3。那么还有 特点 4、5、6、7、9 没有被解释,我们接下来继续探索。

    + load 和 + initialize

    我们回到 _objc_init(void) 函数,最后一行的第二个参数 是 load_images。我们点进去看看

    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
        {
            mutex_locker_t lock2(runtimeLock);
            prepare_load_methods((const headerType *)mh);
        }
    
        // Call +load methods (without runtimeLock - re-entrant)
        call_load_methods();
    }
    

    里面调用了 call_load_methods()。

    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();
            }
    
            // 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);
    
        objc_autoreleasePoolPop(pool);
    
        loading = NO;
    }
    

    请看 do while 循环,代码中给了明确的三步的注释。

    1. 循环的调用类中的 +load 方法,直到所有类中的 +load 方法都调用一遍为止。
    2. 调用分类中的 +load 方法一次
    3. 判断是否有类中的 +load 没有调用,或者是否有分类中的 +load 方法没有调用。

    这基本解释的 特性 5先调用类中的 +load 方法,后调用分类中的 +load 方法 的小点。

    接下来我们看一下 call_class_loads(void) 函数:

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

    里面有个 loadable_class 类型的数组 classes 就是实现了 +load 方法的类,有 cls 和 method 两个元素,method 就是 +load 方法的实现

    struct loadable_class {
        Class cls;  // may be nil
        IMP method;
    };
    

    我们看到 for 循环中使用了 method 直接调用,并没有判断父类,和分类,所有 +load 直接通过函数地址调用,子类不会覆盖父类,分类不会覆盖宿主类。至此 特点 4 基本解释清楚,同时也部分解释了 特性 5

    为了更准确了解 +load 的调用顺序,我们在一个新的工程中添加以下类和分类:

    @interface SuperObject : NSObject
    
    @end
    
    @interface SubObject : SuperObject
    
    @end
    
    @interface SuperObject (Category1)
    
    @end
    
    @interface SuperObject (Category2)
    
    @end
    
    @interface SubObject (Category1)
    
    @end
    
    @interface SubObject (Category2)
    
    @end
    

    对应的实现

    @implementation SuperObject
    
    + (void)load {
        NSLog(@"%@", NSStringFromClass(self.class));
    }
    
    @end
    
    @implementation SubObject
    
    + (void)load {
        NSLog(@"%@", NSStringFromClass(self.class));
    }
    
    @end
    
    @implementation SuperObject (Category1)
    
    + (void)load {
        NSLog(@"%@ Category1", NSStringFromClass(self.class));
    }
    
    @end
    
    @implementation SuperObject (Category2)
    
    + (void)load {
        NSLog(@"%@ Category2", NSStringFromClass(self.class));
    }
    
    @end
    
    @implementation SubObject (Category1)
    
    + (void)load {
        NSLog(@"%@ Category1", NSStringFromClass(self.class));
    }
    
    @end
    
    @implementation SubObject (Category2)
    
    + (void)load {
        NSLog(@"%@ Category2", NSStringFromClass(self.class));
    }
    
    @end
    

    我们定义好了之后不需要任何调用直接运行工程,得到打印

    SuperObject
    SubObject
    SuperObject Category1
    SuperObject Category2
    SubObject Category1
    SubObject Category2
    

    至此 特点 5 完全清楚了。

    下面看关于 + initialize 的相关代码,我首先找到了 initializeNonMetaClass 函数,关于这个类外界如何调用它我们先不去考虑

    /***********************************************************************
    * class_initialize.  Send the '+initialize' message on demand to any
    * uninitialized class. Force initialization of superclasses first.
    **********************************************************************/
    void initializeNonMetaClass(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.
        supercls = cls->superclass;
        if (supercls  &&  !supercls->isInitialized()) {
            initializeNonMetaClass(supercls);
        }
        
        // Try to atomically set CLS_INITIALIZING.
        {
            monitor_locker_t lock(classInitLock);
            if (!cls->isInitialized() && !cls->isInitializing()) {
                cls->setInitializing();
                reallyInitialize = YES;
            }
        }
        
        if (reallyInitialize) {
            // We successfully set the CLS_INITIALIZING bit. Initialize the class.
            
            // Record that we're initializing this class so we can message it.
            _setThisThreadIsInitializingClass(cls);
    
            if (MultithreadedForkChild) {
                // LOL JK we don't really call +initialize methods after fork().
                performForkChildInitialize(cls, supercls);
                return;
            }
            
            // Send the +initialize message.
            // Note that +initialize is sent to the superclass (again) if 
            // this class doesn't implement +initialize. 2157218
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: calling +[%s initialize]",
                             pthread_self(), cls->nameForLogging());
            }
    
            // Exceptions: A +initialize call that throws an exception 
            // is deemed to be a complete and successful +initialize.
            //
            // Only __OBJC2__ adds these handlers. !__OBJC2__ has a
            // bootstrapping problem of this versus CF's call to
            // objc_exception_set_functions().
    #if __OBJC2__
            @try
    #endif
            {
                callInitialize(cls);
    
                if (PrintInitializing) {
                    _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
                                 pthread_self(), cls->nameForLogging());
                }
            }
    #if __OBJC2__
            @catch (...) {
                if (PrintInitializing) {
                    _objc_inform("INITIALIZE: thread %p: +[%s initialize] "
                                 "threw an exception",
                                 pthread_self(), cls->nameForLogging());
                }
                @throw;
            }
            @finally
    #endif
            {
                // Done initializing.
                lockAndFinishInitializing(cls, supercls);
            }
            return;
        }
        
        else if (cls->isInitializing()) {
            // We couldn't set INITIALIZING because INITIALIZING was already set.
            // If this thread set it earlier, continue normally.
            // If some other thread set it, block until initialize is done.
            // It's ok if INITIALIZING changes to INITIALIZED while we're here, 
            //   because we safely check for INITIALIZED inside the lock 
            //   before blocking.
            if (_thisThreadIsInitializingClass(cls)) {
                return;
            } else if (!MultithreadedForkChild) {
                waitForInitializeToComplete(cls);
                return;
            } else {
                // We're on the child side of fork(), facing a class that
                // was initializing by some other thread when fork() was called.
                _setThisThreadIsInitializingClass(cls);
                performForkChildInitialize(cls, supercls);
            }
        }
        
        else if (cls->isInitialized()) {
            // Set CLS_INITIALIZING failed because someone else already 
            //   initialized the class. Continue normally.
            // NOTE this check must come AFTER the ISINITIALIZING case.
            // Otherwise: Another thread is initializing this class. ISINITIALIZED 
            //   is false. Skip this clause. Then the other thread finishes 
            //   initialization and sets INITIALIZING=no and INITIALIZED=yes. 
            //   Skip the ISINITIALIZING clause. Die horribly.
            return;
        }
        
        else {
            // We shouldn't be here. 
            _objc_fatal("thread-safe class init in objc runtime is buggy!");
        }
    }
    

    我特意将这个函数的注释给写了处理,我们看一下最上面两句注释,它的意思就是**为所有 uninitialized 的类发送 “+initialize” 消息。并且在此之前强制 initialization 父类 **

    函数实现中第 4~7 句是一个递归操作,在父类 uninitialized 情况下递归的强制父类 initialization。

    这里面有一个核心函数 callInitialize,我们看一看它的实现

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

    它使用了 objc_msgSend 函数,我们知道这个函数是 OC 调用类的方法所使用的基本函数。故而 callInitialize 做的事情和一个类的普通类方法调用没什么不同,这也就是说,普通类方法子类覆盖父类调用,子类没有实现查找父类,以及分类覆盖宿主类的情况,在 callInitialize 中的情况是一样的。那么 + initialize 唯一与其他类方法不同的地方在于在调用之前回去先调用 uninitialized 的父类。至此 特点 6特点 7 就解释完毕了。下面只剩下 特点 9 没有被解决了。

    关联对象
    ///获取某个对象的关联属性
    id objc_getAssociatedObject(id object, const void *key)
    ///给某个对象添加关联属性
    void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
    ///移除对象所有的关联属性
    void objc_removeAssociatedObjects(id object)
    

    以上三个函数时关于关联对象的三个函数。我们在分类中声明一个属性时,系统不会为我们生产 get set 方法

    以上是分类中添加一个 object 属性报的警告。我们可以在分类的实现中添加 @dynamic 来消除警告,但是这个关键字只是告诉编译器“属性的 setter 与 getter 方法由用户自己实现,你不用管了”,这不过是自欺欺人,你运行时用到该属性时就会崩溃,所有无论如何都要手动去实现 get、set 方法。

    但是分类中没有成员变量,使用_属性名会报错,这样我们在类中重写 set、get 方法的方式在这里就不适用了。好在系统为我们提供了 关联对象 技术解决了这一问题,就是上面的三个函数(主要是前两个)的使用!

    我们分析一下 objc_setAssociatedObject 它就是简单的调用下面的这个函数

    void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
        // This code used to work when nil was passed for object and key. Some code
        // probably relies on that to not crash. Check and handle it explicitly.
        // rdar://problem/44094390
        if (!object && !value) return;
        
        assert(object);
        
        if (object->getIsa()->forbidsAssociatedObjects())
            _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
        
        // retain the new value (if any) outside the lock.
        ObjcAssociation old_association(0, nil);
        id new_value = value ? acquireValue(value, policy) : nil;
        {
            AssociationsManager manager;
            AssociationsHashMap &associations(manager.associations());
            disguised_ptr_t disguised_object = DISGUISE(object);
            if (new_value) {
                // break any existing association.
                AssociationsHashMap::iterator i = associations.find(disguised_object);
                if (i != associations.end()) {
                    // secondary table exists
                    ObjectAssociationMap *refs = i->second;
                    ObjectAssociationMap::iterator j = refs->find(key);
                    if (j != refs->end()) {
                        old_association = j->second;
                        j->second = ObjcAssociation(policy, new_value);
                    } else {
                        (*refs)[key] = ObjcAssociation(policy, new_value);
                    }
                } else {
                    // create the new association (first time).
                    ObjectAssociationMap *refs = new ObjectAssociationMap;
                    associations[disguised_object] = refs;
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                    object->setHasAssociatedObjects();
                }
            } else {
                // setting the association to nil breaks the association.
                AssociationsHashMap::iterator i = associations.find(disguised_object);
                if (i !=  associations.end()) {
                    ObjectAssociationMap *refs = i->second;
                    ObjectAssociationMap::iterator j = refs->find(key);
                    if (j != refs->end()) {
                        old_association = j->second;
                        refs->erase(j);
                    }
                }
            }
        }
        // release the old value (outside of the lock).
        if (old_association.hasValue()) ReleaseValue()(old_association);
    }
    

    我们从 AssociationsManager manager; 这句话开始看先看一看 AssociationsManager 这个 C++ 类。

    class AssociationsManager {
        // associative references: object pointer -> PtrPtrHashMap.
        static AssociationsHashMap *_map;
    public:
        AssociationsManager()   { AssociationsManagerLock.lock(); }
        ~AssociationsManager()  { AssociationsManagerLock.unlock(); }
        
        AssociationsHashMap &associations() {
            if (_map == NULL)
                _map = new AssociationsHashMap();
            return *_map;
        }
    };
    

    它的作用就是管理一个 AssociationsHashMap 类型的数据,没有就创建,有就获取,也就是说通过这个类每次获取的 _map 都是同一个对象。

    代码中先是获取了 AssociationsHashMap 类型的 associations 变量,然后做了一个 disguised_ptr_t disguised_object = DISGUISE(object) 操作,其实这个的意思是把 object 的地址按位取反。它的意义稍后再说

    我们再看 new_value 不为空的分支,第一句话AssociationsHashMap::iterator i = associations.find(disguised_object);就是获取 associations 中的元素,而且是通过 disguised_object 这个值进行的键值查找。由于 disguised_object 是 object 地址的按位取反,所以只要通过这个变量取反后直接获取地址中的内容,就可以获取到 associations 中的元素了,这样就不需要去变量 associations 元素,大大降低了系统的查找时间。这其实是一种哈希算法,OC 中的NSDictionary的查找也是类似的哈希算法,只不过具体的算法不是简单的通过“按位取反”的方式罢了。

    接下来的判断就是看是否取到了 i 元素,如果有,就对原有元素进行操作,没有,就新创建一个元素然后再操作。我们主要看第一个分支,即存在 i 元素的情况。

    这里面又获取了一个元素 refs,是 ObjectAssociationMap 类型的,然后获取里面的元素 j,这个 j 是通过函数传递过来的参数 key 找到的,实际上是一种和 OC NSDictionary 类似的哈希查找方式。j 中的 second 就是属性的旧值,我们可以通过 ObjcAssociation 函数更新旧值。

    我们再看一下 new_value 为空的分支,这里并不是什么都不做,而是 refs->erase(j); 将这个 key 的条目直接擦除了。

    分析了objc_setAssociatedObject代码后我们对关联对象技术有了比较深刻的理解,其他两个函数代码这里就不分析了,为了更好理解关联对象技术,请看下面的关系图

    关联对象.jpg

    我们解决了 特点 9,关于分类的原理就探究到这里。

    相关文章

      网友评论

          本文标题:iOS 分类原理探究

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