当前位置: 首页>编程语言>正文

笔记02

Category的用途:

1.类的拆解:根据不同功能将代码放入不同分类中(模拟多继承),减少单个类的体积;
2.公开“私有方法”(私有方法前向引用):类扩展中已声明私有方法,去分类.h声明;

  • (a) 类扩展中声明私有方法
@interface Person ()
// 类扩展声明的私有方法
- (void)ExtensionPrivateMethod ;
@end

@implementation Person
- (void)ExtensionPrivateMethod {
    NSLog(@"类扩展@interface()有声明,私有方法");
}
@end
  • (b) 分类.h再次声明
#import "Person.h"
@interface Person (Category1)
// .h中声明私有方法,.m中无需实现
- (void)ExtensionPrivateMethod;
@end

#import "Person+Category1.h"
@implementation Person (Category1)
@end
  • (c) 调用私有方法,需要引入类和分类
#import "Person.h"
#import "Person+Category1.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        // 私有方法前向引用
        [person ExtensionPrivateMethod];
    }
    return 0;
}

总结:
1.如果不在分类.h中公开私有方法,可以通过performSelector方式强制调用私有方法或者拿到它的函数指针调用
2.正常的方式调用就需要在分类.h中公开(声明)私有方法,不实现

Person *person = [[Person alloc] init];
[person performSelector:@selector(ExtensionPrivateMethod)];

Category结构

分类不能直接添加成员变量,因为category中没有存储成员变量对应的指针变量;
可以利用关联对象技术实现为category添加成员变量的效果;
分类能添加属性,但是不会生成setter和getter方法;

struct category_t {
    const char *name;                           // 分类的名称
    classref_t cls;                             // 分类所属的列
    struct method_list_t *instanceMethods;      // 添加的实例方法列表
    struct method_list_t *classMethods;         // 添加的类方法列表
    struct protocol_list_t *protocols;          // 添加的协议列表
    struct property_list_t *instanceProperties; // 添加的实例属性列表
    struct property_list_t *_classProperties;   // 类属性(class, nonatomic, strong)
}
  • 关联对象技术实现为分类添加成员变量
#import "Person+Category.h"
#import <objc/runtime.h> // 类相关函数
//#import <objc/message.h> // 消息相关函数

@implementation Person (Category)

// 关联对象 能够为分类添加属性
- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN);
}

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

@end

关联对象API
设置类关联属性 void _object_set_associative_reference(object, key, value, policy)
获取类关联属性 id _object_get_associative_reference(object, key);
移除类所有关联属性 void _object_remove_assocations(object);

  • 设置类关联属性 _object_set_associative_reference(object, key, value, policy)
/**
@param object 准备被关联的对象
@param key 要关联的值对应的key 标识
@param value 关联的值
@param policy 策略
 */
void _object_set_associative_reference(id object, void *key, id value,  uintptr_t policy) {
    // 存放 policy 和 关联值value 默认为空
    ObjcAssociation old_association(0, nil);
                                           
    // 根据 策略policy 对 value 进行加工,按照策略对value进行 copy或者retain
    // newValue ---> 准备关联的值
    id new_value = value acquireValue(value, policy) : nil;
  
    {
        // 关联对象管理类,C++实现一个类  
        // 维护了一个单例Hash表 AssociationsHashMap对象
        AssociationsManager manager;
        
        /**
         初始化一个 AssociationsHashMap 对象 associations
         用来维护对象 和 ObjectAssociationMap 之间的关系
         单例对象 AssociationsHashMap
         */
        AssociationsHashMap &associations(manager.associations());
        
        // 获取关联对象的索引 ---> DISGUISE 对这个指针地址 按位取反
        disguised_ptr_t disguised_object = DISGUISE(object);

        if (new_value) {
            // 根据对象指针查找对应的一个ObjectAssociateMap结构的map
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            
            // 从全局容器中找到了ObjectAssociationMap
            if (i != associations.end()) {
                // secondary table exists  -->  i->second
                // i->first 表示对象指针
                // i->second 表示获取 ObjectAssociationMap
                ObjectAssociationMap *refs = i->second;
                // 根据传递进来的key进行查找
                ObjectAssociationMap::iterator j = refs->find(key);

                if (j != refs->end()) { // ObjcAssociation 找到了
                    old_association = j->second;
                    // 如果关联对象已存在,则通过ObjcAssociation赋新值(替换最新的value)
                    j->second = ObjcAssociation(policy, new_value);
                } else {
                    // 如果关联对象不存在,则创建新的关联对象
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            } else {
                /**
                 create the new association (first time).
                 如果没有 ObjectAssociationMap 表
                 则第一次创建一个 ObjectAssociationMap 表
                 */
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                // 全局容器中的key = disguised_object
                // 全局容器中的value = 这个新创建的map
                associations[disguised_object] = refs;
                /**
                新关联的值 new_value 通过策略 policy 组装成 ObjcAssociation 
                作为新创建好的map, ObjectAssociationMap[key]
                key 传进来的key
                */
                (*refs)[key] = ObjcAssociation(policy, new_value);
                // 最后设置这个对象是有关联对象
                object->setHasAssociatedObjects();
            }
        } else {
            /**
             如果new_value为 空,那么删除该关联对象
             */
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            // 查找到了这个ObjectAssociationMap
            if (i !=  associations.end()) {
                ObjectAssociationMap *refs = i->second;
                // 通过key 到 ObjectAssociationMap中查找
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    // 找到了ObjcAssociation
                    old_association = j->second;
                    // 擦除操作,从ObjectAssociationMap中移除
                    // 所以我们想移除关联对象,可以将关联对象的值设置为nil进行移除
                    refs->erase(j);
                }
            }
        }
    }
    // 释放旧值
    if (old_association.hasValue()) ReleaseValue()(old_association);
}
  • 获取类关联属性 _object_get_associative_reference(object, key)
// 用来得到已有的关联对象
id _object_get_associative_reference(id object, void *key) {
    id value = nil;
    uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
    {
        // 初始化AssociationsManager
        AssociationsManager manager;
        /**
         初始化一个AssociationsHashMap 对象 associations
         用来维护对象和ObjectAssociationMap之间的关系
         */
        AssociationsHashMap &associations(manager.associations());
        // 获取关联对象的索引
        disguised_ptr_t disguised_object = DISGUISE(object);
        // 根据对象指针查找对应的一个ObjectAssociateMap结构的map
        // 通过迭代器 找到对应的 ObjectAssociationMap
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        
        if (i != associations.end()) {
            // i->first 表示对象地址
            // i->second表示获取ObjectAssociationMap
            ObjectAssociationMap *refs = i->second;
            // 通过find(key)找到ObjectAssociationMap
            ObjectAssociationMap::iterator j = refs->find(key);
            if (j != refs->end()) {
                // 获取到ObjcAssociation
                ObjcAssociation &entry = j->second;
                // 取值 取出value和policy
                value = entry.value();
                policy = entry.policy();
                if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
                    objc_retain(value);
                }
            }
        }
    }
    if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
        objc_autorelease(value);
    }
    return value;
}
  • 移除类所有关联对象 _object_remove_assocations(object)
void _object_remove_assocations(id object) {
    vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        if (associations.size() == 0) return;
        disguised_ptr_t disguised_object = DISGUISE(object);
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        if (i != associations.end()) {
            // 找到ObjectAssociationMap 开始遍历
            // 拷贝所有需要删除的关联对象
            ObjectAssociationMap *refs = i->second;
            for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
                elements.push_back(j->second);
            }
            // 删除 ObjectAssociationMap
            delete refs;
            associations.erase(i);
        }
    }
    // 将拷贝的值再次遍历release.
    for_each(elements.begin(), elements.end(), ReleaseValue());
}

Category的加载过程

  • _objc_init() runtime入口函数,进行一些初始化操作
void _objc_init(void) {
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    // 注册了三个回调函数
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
  • map_images() 加锁
void map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[]) {
    // 加锁
    rwlock_writer_t lock(runtimeLock);
    // 完成所有类的注册 和 fixup 等操作,还包括一些初始化工作以及 调用load类方法
    return map_images_nolock(count, paths, mhdrs);
}
  • map_images_nolock() 完成所有类的注册和fixup等工作,还包括一些初始化工作以及调用load类方法
void  map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[]) {
    ......
    ......

    /**
    _read_images 完成两件事
    1、将category和类绑定在一起
    2、重建类的方法列表
     */
    if (hCount > 0) {
        // 完成类的加载,协议的加载,类别的加载等工作
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }
    firstTime = NO;
}
  • _read_images() 完成类、协议、分类的加载等工作

_read_images()中主要完成了两件事情
1、addUnattachedCategoryForClass(...) 将category和类绑定在一起
2、remethodizeClass(...)重建类的方法列表

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) {
    ......
   // 发现分类
   for (EACH_HEADER) {
        // 从编译好的文件获取所有的分类
        category_t **catlist = _getObjc2CategoryList(hi, &count);
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();
        
        // 遍历category列表
        for (i = 0; i < count; i++) {
            category_t *cat = catlist[I];
            // 重映射分类所属的类
            Class cls = remapClass(cat->cls);

            if (!cls) {
                catlist[i] = nil;
                continue;
            }

            bool classExists = NO;
            if (cat->instanceMethods ||  
                cat->protocols  ||  
                cat->instanceProperties)  {
                // 绑定分类和目标类 or 将category添加到分类表中
                addUnattachedCategoryForClass(cat, cls, hi);
                // 判断类是否初始化
                if (cls->isRealized()) {
                    // 重新构建方法列表
                    remethodizeClass(cls);
                    classExists = YES;
                }
            }

            // 把category的类方法和协议添加到类的metaclass上(元类)
            if (cat->classMethods  ||
                cat->protocols  ||  
                (hasClassProperties && cat->_classProperties))  {
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA()->isRealized()) {
                    remethodizeClass(cls->ISA());
                }
            }
        }
    }
    if (DebugNonFragileIvars) {
        realizeAllClasses();
    }
}

// 将category添加到分类表中
static void addUnattachedCategoryForClass(category_t *cat, Class cls, 
                                          header_info *catHeader) {
    runtimeLock.assertWriting();
    // 获取未处理的分类的表(没有把分类中的信息添加到类中去)
    // NXMapTable 是哈希表
    NXMapTable *cats = unattachedCategories();
    category_list *list;

    // 根据类获取对应的分类数组
    list = (category_list *)NXMapGet(cats, cls);
    if (!list) {
        list = (category_list *)
            calloc(sizeof(*list) + sizeof(list->list[0]), 1);
    } else {
        // 扩容
        list = (category_list *)
            realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
    }
    // 把当前分类添加到分类数组中 list中是一系列locstamp_category_t
    list->list[list->count++] = (locstamped_category_t){cat, catHeader};
    // 把新的list插入到分类表中去 key=cls, value=list
    NXMapInsert(cats, cls, list);
}

  • remethodizeClass() 重建类的方法列表
// 重建类的方法列表
static void remethodizeClass(Class cls) {
    category_list *cats;
    bool isMeta;
    runtimeLock.assertWriting();
    // 是否是元类
    isMeta = cls->isMetaClass();
    // 获取类对应的分类数组,并且从分类的哈希表中删除掉分类数组
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
        // 将分类的method、protocol、property添加到class中
        attachCategories(cls, cats, true /*flush caches*/);        
        // 释放分类
        free(cats);
    }
}

// 获取cls中未完成整合的所有分类
static category_list *unattachedCategoriesForClass(Class cls, bool realizing) {
    runtimeLock.assertWriting();
    // 返回对应的分类
    return (category_list *)NXMapRemove(unattachedCategories(), cls);
}
  • attachCategories() 将分类中的方法和属性列表绑定到目标类上

attachCategories 主要是创建一个新的方法列表空间,存放category中的实例方法、类方法、协议方法,然后将这个方法列表交给attachLists处理

static void attachCategories(Class cls, category_list *cats, bool flush_caches) {
    // 对分类进行空判断,如果没有直接return
    if (!cats) return;
    // 判断是否是元类
    bool isMeta = cls->isMetaClass();
    /**
     方法列表 二维数组
     [
        [method_t, method_t],
        [method_t],
        [method_t, method_t,method_t],
        ...
     ]
     */
    // 方法列表
    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));

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

    // 获取宿主类当中的 rw 数据,其中包含宿主类的方法列表信息
    auto rw = cls->data();

    // 主要是针对 分类中关于内存管理相关方法情况下的 一些特殊处理
    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);

    /**
     rw 代表类
     methods 代表类的方法列表
     attachLists 方法的含义是 将含有mcount个元素的mlists拼接到 rw 的methods上
     */

    // 1、创建新的 方法列表 和 目标类中方法列表融合
    // 1、把分类中的方法添加class中的方法列表中去
    rw->methods.attachLists(mlists, mcount);
    free(mlists);

    if (flush_caches  &&  mcount > 0) flushCaches(cls);
    // 2、创建新的 属性列表 和 目标类的属性列表融合
    // 2、把分类中的属性添加class中的属性列表中去
    rw->properties.attachLists(proplists, propcount);
    free(proplists);

    // 3、创建新的 协议列表 和 目标类的协议列表融合
    // 3、将分类中的协议列表添加到class中的协议列表中去
    rw->protocols.attachLists(protolists, protocount);
    free(protolists);
}

  • attachLists() 将类中的方法和分类中的方法放到一个列表中

oldCount:目标类中方法列表长度
addedCount:category方法列表的长度
mommove:将类方法后移addedCount个偏移量
momcpy:将category中方法复制到偏移量从0到addedCount的类方法列表中
所以category中的方法并没有覆盖类中的方法,只是将category中的方法放到了类方法的前面,调用方法的时候,如果category和类中有同名方法,系统会找到前面category中的方法返回并调用。category中的方法优先级高于类中的方法

void attachLists(List* const * addedLists, uint32_t addedCount) {
    if (addedCount == 0) return;
    
    // 有数组的情况下
    if (hasArray()) {
        // many lists -> many lists
        // 列表中原有元素总数 oldCount
        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]));
        
        /**
         内存拷贝
         [
            A ---> [addedLists中的第一个元素]
            B ---> [addedLists中第二个元素]
            C ---> [addedLists中第三个元素]
            [原有的第一个元素]
            [原有的第二个元素]
         ]
         */
        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;

        // 将类方法直接移动到列表的addedCount位置
        if (oldList) array()->lists[addedCount] = oldList;
        // 将Category中的方法拷贝到array数组中位置从0~addedCount-1前面
        memcpy(array()->lists, addedLists,
               addedCount * sizeof(array()->lists[0]));
    }
}

总结:通过Runtime加载某个类的所有category数据,将所有category的方法、属性、协议信息合并到一个大数组中,后参与编译的category数据,会在数组的前面(后编译先调用while(--)),将合并后的category数据(方法、属性、协议)插入到类原来数据的前面

Category的"覆盖"问题

后编译的category中的同名方法会覆盖前面编译的category中的同名方法
category覆盖类中的同名方法
可以通过函数指针调用原有类中的方法

#import "Person.h"
#import "Person+Category1.h"
#import "Person+Category2.h"
#import <objc/runtime.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
  
        Person *person = [[Person alloc] init];
        [person test];

        // 调用person中的test方法
        unsigned int count = 0;
        Method *methodList = class_copyMethodList([Person class], &count);
        
        IMP imp = NULL;
        SEL sel = NULL;
        for (unsigned int i = 0; i < count; i ++) {
            Method method = methodList[i];
            SEL methodSEL = method_getName(method);
            NSString *methodName = [NSString stringWithUTF8String:sel_getName(methodSEL)];
            if ([methodName isEqualToString:@"test"]) {
                imp = method_getImplementation(method);
                sel = method_getName(method);
            }
        }
        ((void(*)(id, SEL))(void *)imp)(person, sel);
        free(methodList);
    }
    return 0;
}

笔记02,第1张
函数指针调用

load方法加载顺序

load_images中主要做了2件事情:
1、发现load方法 prepare_load_methods();
2、调用load方法 call_load_methods();

void load_images(const char *path __unused, const struct mach_header *mh) {
    // 判断是否有load方法,没有load方法直接返回
    if (!hasLoadMethods((const headerType *)mh)) return;

    // 递归锁
    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods 发现load方法
    {
        // 读写锁
        rwlock_writer_t lock2(runtimeLock);
        // 发现load方法
        prepare_load_methods((const headerType *)mh);
    }

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

prepare_load_methods()中schedule_class_load(isa->superclass) 通过递归先将父类中的load方法存入loadable_class,所有得出父类的load优先于子类的load方法调用

void prepare_load_methods(const headerType *mhdr) {
    size_t count, i;
    runtimeLock.assertWriting();

    // 获取非懒加载的类的列表
    classref_t *classlist =  _getObjc2NonlazyClassList(mhdr, &count);
        for (i = 0; i < count; i++) {
        // 核心1:【获得子类和父类中所有的load方法】load 方法如何别处理在这里 先加载父类 再加载子类
        schedule_class_load(remapClass(classlist[i]));
    }

    // 获得非懒加载category的列表 分类没有父类
    category_t **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 忽略弱连接的分类
        // 初始化类信息
        realizeClass(cls);
        assert(cls->ISA()->isRealized());
        // 核心2:添加到loadable_categories
        add_category_to_loadable_list(cat);
    }
}

static void schedule_class_load(Class cls) {
    // 判断是否为空
    if (!cls) return;
    assert(cls->isRealized());  // _read_images should realize
    
    /**
     // load方法已经被执行过
     #define RW_LOADED             (1<<23)
     和下面的 cls->setInfo(RW_LOADED)  对应
     */
    // 判断是否已经添加过load方法到load方法列表中
    if (cls->data()->flags & RW_LOADED) return;

    // 通过递归找到父类的load方法并添加,实际上调用自身的方法
    schedule_class_load(cls->superclass);

    // 将Class和IMP添加到调用列表中
    add_class_to_loadable_list(cls);
    // 设置class状态为已经添加过load方法 意思是设置了一个标志
    cls->setInfo(RW_LOADED); 
}
  • call_load_methods() load方法的调用

通过do-while循环中 call_class_loads()和 call_category_load()可以看出,优先调用类的load方法,再调用分类的调用方法
load方法通过函数指针调用

void call_load_methods(void) {
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();

    do {
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            // 1.先调用类中的+load方法
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        // 2.调用分类的+load方法
        more_categories = call_category_loads();

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}

// 调用类的load方法
static void call_class_loads(void) {
    int i;
    
    // Detach current loadable list.
    // 获取当前loadable_classes列表
    struct loadable_class *classes = loadable_classes;
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;
    
    // Call all +loads for the detached list.
    
    // 从子类到父类,遍历所有类中的 +load 方法
    // 遍历loadable_classes列表,依次执行load方法,只遍历已添加的load方法的列表元素used
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
        load_method_t load_method = (load_method_t)classes[i].method;
        // 如果cls为空,则不执行其load方法
        if (!cls) continue; 

        // 通过函数地址直接调用load方法
        (*load_method)(cls, SEL_load);
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}


https://www.xamrdz.com/lan/5sa2016602.html

相关文章: