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

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

  • 相关阅读:
    一些网站后台模板源码分析 Particleground.js 验证码
    C# 接口的作用浅谈举例(转)
    C# 批量修改文件名
    12306抢票软件相关接口以及数据格式
    StackExChange.Redis for C# 中文文档
    asp.net core 3+ 修改view后自动重新编译运行
    ASP.NET MVC https全局配置
    c#DateTime与unix时间戳互相转换
    mongodb创建管理员用户
    Office365激活方法(无需密钥)
  • 原文地址:https://www.cnblogs.com/muzichenyu/p/14065239.html
Copyright © 2020-2023  润新知