美文网首页
iOS Category

iOS Category

作者: nickNic | 来源:发表于2021-05-14 09:48 被阅读0次

    一、Category的实现原理
    1、Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法,类方法,属性,协议信息.
    2、在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象,元类信息中)

    Category结构体的定义:

    struct category_t {
    
    constchar*name;//类的名字(name)
    
    classref_t cls;//类(cls)
    
    struct method_list_t *instanceMethods; //category中所有给类添加的实例方法的列表(instanceMethods)
    
    structmethod_list_t *classMethods;//category中所有添加的类方法的列表(classMethods)
    
    structprotocol_list_t *protocols; //category实现的所有协议的列表(protocols)
    
    structproperty_list_t *instanceProperties;//category中添加的所有属性(instanceProperties)
    
    };
    

    从category的定义也可以看出category可以添加实例方法,类方法;可以遵守协议,添加属性;但无法添加实例变量。

    添加方法列表的时候是后添加的在新形成的列表前部,这也是为什么在有多个category中有同名方法时,后编译的在调用时会“覆盖”前面已编译的方法。其实方法本身并没有被覆盖,只是调用的时候是从上而下查找方法列表。

       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;
            array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
            newArray->count = newCount;
            array()->count = newCount;
            将以前的数组和新的分类数组合并
            for (int i = oldCount - 1; i >= 0; I--)
                newArray->lists[i + addedCount] = array()->lists[I]; 遍历把以前的信息放入到新数组最后的位置
            for (unsigned i = 0; i < addedCount; i++)通过遍历把新的信息按照递增放入到新数组位置
                newArray->lists[i] = addedLists[I];  
            free(array());
            setArray(newArray);
            validate();
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
            validate();
        } 
        else {
            // 1 list -> many lists
            Ptr<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;
            for (unsigned i = 0; i < addedCount; I++)
                array()->lists[i] = addedLists[I];
            validate();
        }
    }
    

    说到Category方法的调用可以看看CompileSources文件放的位置

    86CC2A34-76B5-40C7-9D6D-C3AE272FDE4F.png

    这个时候日志打印对象方法 test方法LCPersonTest1
    之所以是LCPersonTest1调用是因为它会把后放入的位置放到分类数组的首位然后是Test2而我们的LCPerson是放在数组的最后位置 按照这个方
    式可参考源码

    void  map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
    {
    static bool firstTime = YES;
    header_info *hList[mhCount];
    uint32_t hCount;
    size_t selrefCount = 0;
    
    // Perform first-time initialization if necessary.
    // This function is called before ordinary library initializers. 
    // fixme defer initialization until an objc-using image is found?
    if (firstTime) {
        preopt_init();
    }
    
    if (PrintImages) {
        _objc_inform("IMAGES: processing %u newly-mapped images...\n", mhCount);
    }
    
    
    // Find all images with Objective-C metadata.
    hCount = 0;
    
    // Count classes. Size various table based on the total.
    int totalClasses = 0;
    int unoptimizedTotalClasses = 0;
    {
        uint32_t i = mhCount;
        I-- 按照从大到小遍历在编译的过程中Test1是最后的
        while (i--) {
            const headerType *mhdr = (const headerType *)mhdrs[I];
    
            auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
            if (!hi) {
                // no objc data in this entry
                continue;
            }
            
            if (mhdr->filetype == MH_EXECUTE) {
                // Size some data structures based on main executable's size
    #if __OBJC2__
                // If dyld3 optimized the main executable, then there shouldn't
                // be any selrefs needed in the dynamic map so we can just init
                // to a 0 sized map
                if ( !hi->hasPreoptimizedSelectors() ) {
                  size_t count;
                  _getObjc2SelectorRefs(hi, &count);
                  selrefCount += count;
                  _getObjc2MessageRefs(hi, &count);
                  selrefCount += count;
                }
    #else
                _getObjcSelectorRefs(hi, &selrefCount);
    #endif
                
    #if SUPPORT_GC_COMPAT
                // Halt if this is a GC app.
                if (shouldRejectGCApp(hi)) {
                    _objc_fatal_with_reason
                        (OBJC_EXIT_REASON_GC_NOT_SUPPORTED, 
                         OS_REASON_FLAG_CONSISTENT_FAILURE, 
                         "Objective-C garbage collection " 
                         "is no longer supported.");
                }
    #endif
            }
            
            hList[hCount++] = hi;  这个时候把Test1放到了首位。然后Test2 最后Test 
            
            if (PrintImages) {
                _objc_inform("IMAGES: loading image for %s%s%s%s%s\n", 
                             hi->fname(),
                             mhdr->filetype == MH_BUNDLE ? " (bundle)" : "",
                             hi->info()->isReplacement() ? " (replacement)" : "",
                             hi->info()->hasCategoryClassProperties() ? " (has class properties)" : "",
                             hi->info()->optimizedByDyld()?" (preoptimized)":"");
            }
        }
    }
    
    // Perform one-time runtime initialization that must be deferred until 
    // the executable itself is found. This needs to be done before 
    // further initialization.
    // (The executable may not be present in this infoList if the 
    // executable does not contain Objective-C code but Objective-C 
    // is dynamically loaded later.
    if (firstTime) {
        sel_init(selrefCount);
        arr_init();
    
    #if SUPPORT_GC_COMPAT
        // Reject any GC images linked to the main executable.
        // We already rejected the app itself above.
        // Images loaded after launch will be rejected by dyld.
    
        for (uint32_t i = 0; i < hCount; i++) {
            auto hi = hList[I];
            auto mh = hi->mhdr();
            if (mh->filetype != MH_EXECUTE  &&  shouldRejectGCImage(mh)) {
                _objc_fatal_with_reason
                    (OBJC_EXIT_REASON_GC_NOT_SUPPORTED, 
                     OS_REASON_FLAG_CONSISTENT_FAILURE, 
                     "%s requires Objective-C garbage collection "
                     "which is no longer supported.", hi->fname());
            }
        }
    #endif
    
    #if TARGET_OS_OSX
        // Disable +initialize fork safety if the app is too old (< 10.13).
        // Disable +initialize fork safety if the app has a
        //   __DATA,__objc_fork_ok section.
    
        if (!dyld_program_sdk_at_least(dyld_platform_version_macOS_10_13)) {
            DisableInitializeForkSafety = true;
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: disabling +initialize fork "
                             "safety enforcement because the app is "
                             "too old.)");
            }
        }
    
        for (uint32_t i = 0; i < hCount; i++) {
            auto hi = hList[I];
            auto mh = hi->mhdr();
            if (mh->filetype != MH_EXECUTE) continue;
            unsigned long size;
            if (getsectiondata(hi->mhdr(), "__DATA", "__objc_fork_ok", &size)) {
                DisableInitializeForkSafety = true;
                if (PrintInitializing) {
                    _objc_inform("INITIALIZE: disabling +initialize fork "
                                 "safety enforcement because the app has "
                                 "a __DATA,__objc_fork_ok section");
                }
            }
            break;  // assume only one MH_EXECUTE image
        }
    #endif
    
    }
    
    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }
    
    firstTime = NO;
    
    // Call image load funcs after everything is set up.
    for (auto func : loadImageFuncs) {
        for (uint32_t i = 0; i < mhCount; i++) {
            func(mhdrs[I]);
        }
    }
    }
    

    二、Category和class Extension的区别是什么?
    class Extension在编译的时候,它的数据就已经包含在类信息中.
    而Category在运行时才会将数据合并到类信息中. 在编译的时候,它有多少个分类就会生成多少个category_t.等到运行的时候才会通过runtime将Category的数据,合并到类信息中(类对象,元类信息中).

    相关文章

      网友评论

          本文标题:iOS Category

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