OC的分类方法调用原理

在使用OC开发中,我们经常使用分类为一些不方便修改的类,添加分类,达到为类添加"属性"和方法的目的,下面是为LBPerson类添加分类的代码:

#import "LBPerson.h"

NS_ASSUME_NONNULL_BEGIN

@interface LBPerson (Animation)

- (void)run;
@end

NS_ASSUME_NONNULL_END

#import "LBPerson+Animation.h"

@implementation LBPerson (Animation)

- (void)run {
    NSLog(@"LBPerson+Animation.h");
}
@end

如果分类中的方法和原类中方法名称相同,会优先调用分类中的方法。

LBPerson中代码如下:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface LBPerson : NSObject
- (void)run;
@end

NS_ASSUME_NONNULL_END
#import "LBPerson.h"

@implementation LBPerson

- (void)run {
    NSLog(@"LBPerson run");
}
@end

LBPerson+Animation中代码如下:

#import "LBPerson.h"

NS_ASSUME_NONNULL_BEGIN

@interface LBPerson (Animation)

- (void)run;
@end

NS_ASSUME_NONNULL_END
#import "LBPerson+Animation.h"

@implementation LBPerson (Animation)

- (void)run {
    NSLog(@"Animation run");
}
@end

在main方法中调用person对象的run方法代码以及打印如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        LBPerson *person = [[LBPerson alloc] init];
        [person run];
    }
    return 0;
}

// 输出结果
Animation run

如果两个分类中含有相同的方法,则会按照编译的顺序,编译靠后的分类会被调用

下面就从runtime的源码分析为什么会按照上述规则进行调用:

// cls 本类
// cats_list 存储分类数组
// cats_count 存储分类的数量
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{
    if (slowpath(PrintReplacedMethods)) {
        printReplacements(cls, cats_list, cats_count);
    }
    if (slowpath(PrintConnecting)) {
        _objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
                     cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
                     cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
    }

    /*
     * Only a few classes have more than 64 categories during launch.
       在启动期间只有少数类拥有超过64个分类
     * This uses a little stack, and avoids malloc.
     * 使用少量栈避免分类
     * Categories must be added in the proper order, which is back
       分类肯定会按照合适的顺序被添加,这个顺序就是从后向前
     * to front. To do that with the chunking, we iterate cats_list
       为了做这个组块,我们迭代分类数组从前到后
     * from front to back, build up the local buffers backwards,
       建造一个向后的本地缓冲
     * and call attachLists on the chunks. attachLists prepends the
       调用附加数组在多个组块上
     * lists, so the final result is in the expected order.
       附加数组频道list上,所以最终的结果就是按照期待的顺序
     */
    constexpr uint32_t ATTACH_BUFSIZ = 64;
    method_list_t   *mlists[ATTACH_BUFSIZ]; // 类方法数组
    property_list_t *proplists[ATTACH_BUFSIZ]; // 对象方法数组
    protocol_list_t *protolists[ATTACH_BUFSIZ]; //协议数组

    uint32_t mcount = 0;
    uint32_t propcount = 0;
    uint32_t protocount = 0;
    bool fromBundle = NO;
    bool isMeta = (flags & ATTACH_METACLASS); // 是否是元类
    auto rwe = cls->data()->extAllocIfNeeded(); // 找到本类的rw 并且判断是否需要额外分配
    // 遍历分类数组
    for (uint32_t i = 0; i < cats_count; i++) {
        auto& entry = cats_list[i]; // 具体的分类
        // 取出类方法
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            if (mcount == ATTACH_BUFSIZ) {
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
                // 添加到本来的方法列表中
                rwe->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
        // 取出对象方法
        property_list_t *proplist =
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            if (propcount == ATTACH_BUFSIZ) {
                rwe->properties.attachLists(proplists, propcount);
                propcount = 0;
            }
            proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
        }
        // 取出协议
        protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
        if (protolist) {
            if (protocount == ATTACH_BUFSIZ) {
                rwe->protocols.attachLists(protolists, protocount);
                protocount = 0;
            }
            protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
        }
    }

    if (mcount > 0) {
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle);
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        if (flags & ATTACH_EXISTING) flushCaches(cls);
    }

    rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);

    rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}

决定方法调用顺序就是下面源码:

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

上述源码注释纯属个人的理解

 

posted @ 2020-11-30 23:06  木子沉雨  阅读(288)  评论(0编辑  收藏  举报