美文网首页Objective-c
iOS进阶回顾二「Category的研究」

iOS进阶回顾二「Category的研究」

作者: Coder东 | 来源:发表于2019-08-26 22:25 被阅读0次

    iOS进阶回顾一「Runtime」

    分类相信大家都不陌生,经常会用到但是它到底是怎么实现的呢?
    • 首先我们先新建一个分类:
    //
    //  SFPerson+Helper.h
    //  Category
    //
    //  Created by 随风流年 on 2019/8/26.
    //  Copyright © 2019 随风流年. All rights reserved.
    //
    
    #import "SFPerson.h"
    
    NS_ASSUME_NONNULL_BEGIN
    @interface SFPerson (Helper)
    -(void)run;
    @end
    
    NS_ASSUME_NONNULL_END
    
    
    #import "SFPerson+Helper.h"
    @implementation SFPerson (Helper)
    -(void)run {
        NSLog(@"SFPerson (Helper) run");
    }
    @end
    
    • 然后利用终端切换到当前文件夹下输入:
      xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc SFPerson+Helper.m
      就可以得到编译后的.cpp文件得到如下图所示的分类的底层结构:
    image.png
    从上图可以看到分类是一个category_t 的struct每一个分类都会生成这样的一个结构体
    继续查看我们会看到一个变量:
    B5832FA14B2395EE9124029BC6C30423.png
    在这个结构体变量中又可以查看到我们的方法 1641451F798259ABC9A60DFD18430F5A.png
    由此可见分类是生成一个category_t的一个结构体,并没有合并到类中

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

    查看底层实现:

    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);
    }
    
    附加的实现 D65FDC798C1F6C5037D3441B03D731EA.png
    • 我们可以看到 附加时,里面 有一个realloc方法重新分配内存,利用oldCount + addedCount 对数组进行扩容。利用memmovememcpy进行地址的移动和拷贝,将原来列表的位置移动或者拷贝到数组的后面,使分类的列表放在数组前面的位置
     memcpy(array()->lists, addedLists, 
                       addedCount * sizeof(array()->lists[0]));
    

    所以同样的方法会优先调用分类

    • 类的扩展也简单说下:
    • 类的扩展是(匿名分类\类扩展),它一编译就合并到类中,可用于把属性或者成员变量私有化,外部不可以方法在.m中建立 @interface SFPerson()进行声明属性方法

    相关文章

      网友评论

        本文标题:iOS进阶回顾二「Category的研究」

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