iOS Category

2021-05-14  本文已影响0人  nickNic

一、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的数据,合并到类信息中(类对象,元类信息中).

上一篇下一篇

猜你喜欢

热点阅读