美文网首页
Category源码

Category源码

作者: helinyu | 来源:发表于2022-02-14 15:16 被阅读0次

Category是什么?
是xcode的苹果开发中提供一种给类性增加功能的机制。

Category可以做什么?
<1>Category可以添加方法、属性
但是,属性不会自动生成对应的成员变量,不可以声明命令,使用了绑定的机制。 —— 添加的更多的是公共的功能而不是业务功能。
<2>重写替换条原来的系统方法: 类中有和原有类同名的方法, 会优先调用分类中的方法, 就是说会忽略原有类的方法。

局限性

1> 不可以扩充成员变量, @protoperty 置灰生成setter和getter声明,不会生成setter和getter的实现以及成员变量。
2> 多个category中都有同名的方法,会出现问题, 因为category同名方法会优先找到, 所以我们可能因为编译器上的差异而有所不同的结果,所以,应该避免category的方法和系统的方法相同,多个category中存在同名的方法,运行时到底调用哪个方法由编译器决定,最后一个参与编译的方法会被调用。

什么时候使用category 而不是继承?
1、 系统上的类,我们想直接给这个类提供额外的属性行为,(而不是继承,对系统已有的子类并没有影响。eg:NSNumber)
2、减少单个类的体积
3、模拟多继承
4、把静态库的私有方法公开。

Category使用

// 定义了name/age属性,  通过objc_assication 来进行绑定值
@interface NSObject (Add)
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
- (void)action; // 定义额外的方法
@end

#import "NSObject+Add.h"
#import <objc/runtime.h>

@implementation NSObject (Add)

- (void)setAge:(NSInteger)age {
    objc_setAssociatedObject(self, "age.key", @(age), OBJC_ASSOCIATION_ASSIGN);
}

- (NSInteger)age {
   return [objc_getAssociatedObject(self, "age.key") integerValue];
}

- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, "name.key", name, OBJC_ASSOCIATION_COPY);
}

- (NSString *)name {
    return objc_getAssociatedObject(self, @"name.key");
}

- (void)action;
{
//     额外的方法
}
@end

定义了额外的属性方法

策略对应关系 常见的设置key的方式

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

1> 程序在运行的时候添加到类对象、元类对象中

程序启动之后的流程图

从上面的柳柳成可以看到开始是通过load_images 方法里面执行加载到有关的内容。
最后的主要方法是: attachCategories ——> attachLists 方法


// 加载categories
static void load_categories_nolock(header_info *hi) {
    bool hasClassProperties = hi->info()->hasCategoryClassProperties(); // 有分类属性

    size_t count;
    auto processCatlist = [&](category_t * const *catlist) {
        for (unsigned i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls);
            locstamped_category_t lc{cat, hi};

            if (!cls) {
                // Category's target class is missing (probably weak-linked).
                // Ignore the category.
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class",
                                 cat->name, cat);
                }
                continue;
            }

            // Process this category.
            if (cls->isStubClass()) {
                // Stub classes are never realized. Stub classes
                // don't know their metaclass until they're
                // initialized, so we have to add categories with
                // class methods or properties to the stub itself.
                // methodizeClass() will find them and add them to
                // the metaclass as appropriate.
                if (cat->instanceMethods ||
                    cat->protocols ||
                    cat->instanceProperties ||
                    cat->classMethods ||
                    cat->protocols ||
                    (hasClassProperties && cat->_classProperties))
                {
                    objc::unattachedCategories.addForClass(lc, cls);
                }
            } else {
                // First, register the category with its target class.
                // Then, rebuild the class's method lists (etc) if
                // the class is realized.
// 给类对象添加方法和属性
                if (cat->instanceMethods ||  cat->protocols
                    ||  cat->instanceProperties)
                {
                    if (cls->isRealized()) {
                        attachCategories(cls, &lc, 1, ATTACH_EXISTING);
                    } else {
                        objc::unattachedCategories.addForClass(lc, cls);
                    }
                }
// 这里给元类添加了方法 、 类属性
                if (cat->classMethods  ||  cat->protocols
                    ||  (hasClassProperties && cat->_classProperties))
                {
                    if (cls->ISA()->isRealized()) {
                        attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
                    } else {
                        objc::unattachedCategories.addForClass(lc, cls->ISA());
                    }
                }
            }
        }
    };

    processCatlist(hi->catlist(&count));
    processCatlist(hi->catlist2(&count));
}

// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order, 
// oldest categories first.
 // cls 对应的类
//  cats_list 分类的列表
// cats_count 分类的数目 
// flags 标记
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.
     * 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.
     */
    constexpr uint32_t ATTACH_BUFSIZ = 64;
    method_list_t   *mlists[ATTACH_BUFSIZ]; // 方法列表 [methods,[],[]] 是一个二维数组
    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(); // 额外分配内存如果需要

    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) { //  如果足够了64个对象就添加一次,不过这个概率很小
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__); 
                rwe->methods.attachLists(mlists, mcount); //  这里扩展了内存
                mcount = 0;
            }
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;//从后面开始添加 (所以,后面添加的就是在前面)
            fromBundle |= entry.hi->isBundle(); // 为什么有fromBundle的判断
        }

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

// 数目不够64的,就在这里进行添加
    if (mcount > 0) { // 方法列表
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
                           NO, fromBundle, __func__);
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        if (flags & ATTACH_EXISTING) {
            flushCaches(cls, __func__, [](Class c){
                // constant caches have been dealt with in prepareMethodLists
                // if the class still is constant here, it's fine to keep
                return !c->cache.isConstantOptimizedCache();
            });
        }
    }

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

PS: 和旧的版本对比: 不再使用realloc和memmove以及memcpy, 而是直接使用malloc 和for循环处理。
优点:
1> 原来的方法, realloc 这个方法可能在原来的基础分配失败,然后调用malloc,同事会进行数据的拷贝。 这样就多了一次数据的拷贝
2> 不再使用memmove以及memcpy, 是直接可以使用for循环可以替换掉。
3> 内存块的连续, 查找方法更加快速。

malloc / calloc / realloc 之间的区别
https://zhuanlan.zhihu.com/p/87061787
https://zhuanlan.zhihu.com/p/384034790
https://zhuanlan.zhihu.com/p/57863097

Category 和Class Extension的区别
Class Category 在编译的时候,它的数据就已经包含在类信息中
Category 是在运行的时候,才会将数据合并到类信息中。

category 的方法查找
其实就是两个for的循环进行查找有关的内容。

for(xx) {
for (xx) {
xxx 查找到对应的方法
}
}

Category中的load方法

1>+load 方法会在runtime加载类、分类时调用
2> 每个类、分类的+load,在程序运行过程中只调用一次

调用顺序
1、先调用类的+load
《1》按照偏移的先婚后顺序调用(先编译、先调用)
《2》调用子类的+load之前先调用父类的+load

2、再调用分类的+load
《1》按照编译先后顺序调用(先编译先调用)

面试题目:category中有load方法吗? load方法是怎么调用的? load方法能继承吗?

  1. 有load方法
  2. load 方法在runtime加载类、分类的时候调用
  3. 可以继承的 (一般不会主动调用) 消息发送机制

源码解析

+load 方法调用的方法
《0》 加载镜像
void load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
        didInitialAttachCategories = true;
        loadAllCategories(); // 加载categories的内容
    }

    // Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh); 
        // 0) 准备加载的方法
    }

    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods(); // 1)调用load方法
}

《1》 准备+load方法
void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

    runtimeLock.assertLocked();

  //0)获取类数组, 然后遍历类加载对应的类信息
    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count); /
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i])); 
    }

// 分类的加载到队列里面面
    category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[i];
        Class cls = remapClass(cat->cls);
        if (!cls) continue;  // category for ignored weak-linked class
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class extensions and categories on Swift "
                        "classes are not allowed to have +load methods");
        }
        realizeClassWithoutSwift(cls, nil);  // 保证这个类已经实现了
        ASSERT(cls->ISA()->isRealized());
        add_category_to_loadable_list(cat); // 添加category到loadable_list中
    }
}

添加类到加载队列中

// cls must already be connected.
// 调用类的+load方法的信息
static void schedule_class_load(Class cls)
{
    if (!cls) return;
    ASSERT(cls->isRealized());  // _read_images should realize 
// RW_LOADED 表示已经调用过load了
    if (cls->data()->flags & RW_LOADED) return; 

    // Ensure superclass-first ordering
    schedule_class_load(cls->getSuperclass()); //0) 递归调用,先调用父类

    add_class_to_loadable_list(cls); // 1)在调用子类 ,添加到loadable_list 里面
    cls->setInfo(RW_LOADED);  // 设置为已经调用过
}

/***********************************************************************
* add_class_to_loadable_list
* Class cls has just become connected. Schedule it for +load if
* it implements a +load method.
**********************************************************************/
void add_class_to_loadable_list(Class cls)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = cls->getLoadMethod(); // 实现一个load的方法
    if (!method) return;  // Don't bother if cls has no +load method , 没有load的方法不处理
    
    if (PrintLoading) {
        _objc_inform("LOAD: class '%s' scheduled for +load", 
                     cls->nameForLogging());
    }
//     实现了16个分类为一个周期,如果 loadable_classes_allocated = 16 下一个就是48 2的N次方扩大内存
    if (loadable_classes_used == loadable_classes_allocated) {
        loadable_classes_allocated = loadable_classes_allocated*2 + 16;
        loadable_classes = (struct loadable_class *)
            realloc(loadable_classes,
                              loadable_classes_allocated *
                              sizeof(struct loadable_class));
    }

    loadable_classes[loadable_classes_used].cls = cls; // 类
    loadable_classes[loadable_classes_used].method = method; // Load 方法
    loadable_classes_used++; //加载了一个新的类
}
// 上面几个方法是将类的的+load信息加载到了loadable_classes 数组里面

添加方法到分类里面

/***********************************************************************
* add_category_to_loadable_list
* Category cat's parent class exists and the category has been attached
* to its class. Schedule this category for +load after its parent class
* becomes connected and has its own +load method called.
**********************************************************************/
void add_category_to_loadable_list(Category cat) // 添加分类到loadable列表中
{
    IMP method;

    loadMethodLock.assertLocked();

    method = _category_getLoadMethod(cat);

    // Don't bother if cat has no +load method
    if (!method) return;

    if (PrintLoading) {
        _objc_inform("LOAD: category '%s(%s)' scheduled for +load", 
                     _category_getClassName(cat), _category_getName(cat));
    }
//     这样就十号线了16个分类为一个周期
    if (loadable_categories_used == loadable_categories_allocated) {
        loadable_categories_allocated = loadable_categories_allocated*2 + 16;
        loadable_categories = (struct loadable_category *)
            realloc(loadable_categories,
                              loadable_categories_allocated *
                              sizeof(struct loadable_category));
    }

    loadable_categories[loadable_categories_used].cat = cat;
    loadable_categories[loadable_categories_used].method = method;
    loadable_categories_used++;  //category对应的索引
}
typedef void(*load_method_t)(id, SEL);  // 调用方法

// 可加载类的数据结构
struct loadable_class {
    Class cls;  // 类
    IMP method; // +load 方法
};

// 可加载的分类数据结构
struct loadable_category {
    Category cat;  // 分类
    IMP method; // +load 方法
};

// 加载类以及+load方法执行
// List of classes that need +load called (pending superclass +load)
// This list always has superclasses first because of the way it is constructed
static struct loadable_class *loadable_classes = nil; // 加载数组
static int loadable_classes_used = 0;// 已经加载可用于执行+load的数目
static int loadable_classes_allocated = 0; // 分配的空间大小

// 分类的
// List of categories that need +load called (pending parent class +load)
static struct loadable_category *loadable_categories = nil;
static int loadable_categories_used = 0;
static int loadable_categories_allocated = 0;

PS: 看了上面的内容, 可以判断有关的load方法的执行先后顺序。


initialize 方法的调用

+initialize 在类第一次接收到消息的时候调用。
调用顺序

  1. 先调用父类的+initialize ,再调用子类的+initialize
//简略核心代码 
void initializeNonMetaClass(Class cls)
{
    // Make sure super is done initializing BEFORE beginning to initialize cls.
    // See note about deadlock above.
    supercls = cls->getSuperclass();
    if (supercls  &&  !supercls->isInitialized()) {
        initializeNonMetaClass(supercls); // 递归调用 , 保证了父类先初始化
    }
    
    if (reallyInitialize) {
            callInitialize(cls); // 这里调用了initialize方法
    }
}

void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
    asm("");
}

PS: 在objc-initialize.mm 文件里面, 处理了初始化的逻辑。


load /initialize 方法的区别是什么? 它们在category中的调用顺序? 以及出现继承时它们之间的调用过程?

区别:

1)调用方式:
+load 是根据函数地址直接调用
initialize 是通过objc_msgSend 调用

2)调用时刻
1>load 是runtime加载类、分类的时候调用(只会调用一次)
2>initialize 是类第一次吃接收到消息的时候调用,每个类只会initialize一次(因为是msgsend 有可能子类没有实现这个方法会去调用父类)

调用顺序:
load:
1> 先调用类的load
先编译的类,优先调用load
调用子类的load之前,会调用父类的load

1> 再调用分类的load
先编译的分类,,优先调用load

  1. initialize
    1> 先初始化父类
    2> 再调用子类,如果子类没有实现,就调用父类的方法。

《1》initialize 是通过objc_msgSend 调用, 所以有对应的查找过程, +load是直接通过函数的地址来调用的。
《2》如果子类没有实现+initialize, 会调用父类的+initialize(所以,父类的+initialize会多次调用)
《3》如果分类实现了+initialize , 就覆盖类本身的+initialize。[其实就是方法查找的过程]


给Category添加属性,实现类似有成员变量的效果

// (1)通过获取静态常量的地址
static const void *MJNameKey = &MJNameKey;
- (void)setName:(NSString *)name
{
    objc_setAssociatedObject(self, MJNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name
{
    return objc_getAssociatedObject(self, MJNameKey);
}
// (2)字符串的常量
#define MJNameKey @"name"
- (void)setName:(NSString *)name
{
    objc_setAssociatedObject(self, MJNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name
{
    return objc_getAssociatedObject(self, MJNameKey);
}
// (3)字符常量地址
static const char MJNameKey;
static const char MJWeightKey;
- (void)setName:(NSString *)name
{
    objc_setAssociatedObject(self, &MJNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name
{
    return objc_getAssociatedObject(self, &MJNameKey);
}
// (4)方法地址
- (void)setName:(NSString *)name
{
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name
{
    // _cmd == @selector(name) 隐式参数
    return objc_getAssociatedObject(self, _cmd);
}

Association 原理

相关文章

网友评论

      本文标题:Category源码

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