11.iOS底层之类与分类搭配加载分析

232 阅读5分钟

上篇文章iOS底层之类的加载我们分析了类的加载过程,这里我们来探索下分类的加载情况

一、初探

我们给类Person写个分类Person+test,然后clang -rewrite-objc Person+test.m -o category.cpp,打开category.cpp,定位到最下面

static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
	&_OBJC_$_CATEGORY_Person_$_test,
};

可见分类是存储在MachO文件的__DATA段的__objc_catlist中,分类是_category_t这样一个静态的结构体,我们在objc源码搜索category_t,看看它的结构

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;
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
  • name:类的名字,不是分类的名字
  • cls:类对象
  • instanceMethods:分类上存储的实例方法
  • classMethods:分类上存储的类方法
  • protocols:分类上所实现的协议
  • instanceProperties:分类所定义的实例属性(一般通过关联对象来实现)
  • _classProperties:分类所定义的类属性

为什么分类要将实例方法和类方法分开存呢?

  • 因为类和元类之前在不断编译,实例方法存在类中,类方法存在元类中,已经确定好其方法归属的地方
  • 而分类是后面才加进来的

二、分类的加载

分类分为懒加载和非懒加载,类也有懒加载和非懒加载之分,这样搭配就分为4中情况了

为了更好的跟中研究的分类,分别在添加分类的相关方法addUnattachedCategoryForClassattachCategoriesmethodizeClass等中添加相关的打印:

const char *cname = cls->demangledName();
const char *oname = "LGTeacher";
if (cname && (strcmp(cname, oname) == 0)) {
    printf("addUnattachedCategoryForClass :%s \n",cname);
}
const char *cname = ro->name;
const char *oname = "LGTeacher";
if (strcmp(cname, oname) == 0) {
   printf("methodizeClass :%s \n",cname);
}

再配合相关的函数调用栈,就能清晰的知道分类的加载流程了

1.非懒加载分类 + 非懒加载类

打印了2次是因为realizeClassWithoutSwift里面会递归父类和元类

supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));
  • _read_images
  • realizeClassWithoutSwift
  • methodlizeClass
  • addUnattachedCategoryForClass
  • remethodizeClass 实现类信息
  • attachCategories 加载分类数据进来`

2.非懒加载分类 + 懒加载类

本来懒加载类是第一次发送消息才加载的,但分类是非懒加载的,所以把类的加载提前在prepare_load_methods

  • _read_images
  • addUnattachedCategoryForClass
  • prepare_load_methods 提前到这里加载类信息
  • realizeClassWithoutSwift
  • methodlizeClass
  • attachCategories

3.懒加载分类 + 非懒加载类

  • _read_images
  • realizeClassWithoutSwift
  • methodlizeClass
  • 直接在相应 data->ro 不进addUnattachedCategoryForClass

4.懒加载分类 + 懒加载类

都是懒加载的,所以类是在第一次发送消息时才加载

  • lookUpImpOrForward 消息发送的时候
  • realizeClassWithoutSwift
  • methodlizeClass
  • 直接在相应 data->ro 不进addUnattachedCategoryForClass

小结: 当分类为懒加载时,编译时就确定,非懒加载时,运行时才确定

三、动态创建类

// 1: 动态创建类
Class LGPerson = objc_allocateClassPair([NSObject class], "LGPerson", 0);
// 2: 添加成员变量 1<<aligment
// ivar - ro - ivarlist
class_addIvar(LGPerson, "lgName", sizeof(NSString *), log2(sizeof(NSString *)), "@");
// 3: 注册到内存
objc_registerClassPair(LGPerson);

// cls->ISA()->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING);
// cls->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING);

// 3.1 添加property - rw
lg_class_addProperty(LGPerson, "subject");
lg_printerProperty([LGTeacher class]);
lg_printerProperty(LGPerson);

// 3.2 添加setter  +  getter 方法
class_addMethod(LGPerson, @selector(setSubject:), (IMP)lgSetter, "v@:@");
class_addMethod(LGPerson, @selector(subject), (IMP)lgName, "@@:");

// 开始使用
id person = [LGPerson alloc];
[person setValue:@"KC" forKey:@"lgName"];
NSLog(@"%@",[person valueForKey:@"lgName"]);

id teacher = [LGTeacher alloc];
[teacher setValue:@"iOS" forKey:@"subject"];
NSLog(@"%@",[teacher valueForKey:@"subject"]);

[person setValue:@"master" forKey:@"subject"];
NSLog(@"%@",[person valueForKey:@"subject"]);


void lgSetter(NSString *value){
    printf("%s/n",__func__);
}

NSString *lgName(){
    printf("%s/n",__func__);
    return @"master NB";
}

void lg_class_addProperty(Class targetClass , const char *propertyName){
    objc_property_attribute_t type = { "T", [[NSString stringWithFormat:@"@\"%@\"",NSStringFromClass([NSString class])] UTF8String] }; //type
    objc_property_attribute_t ownership0 = { "C", "" }; // C = copy
    objc_property_attribute_t ownership = { "N", "" }; //N = nonatomic
    objc_property_attribute_t backingivar  = { "V", [NSString stringWithFormat:@"_%@",[NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding]].UTF8String };  //variable name
    objc_property_attribute_t attrs[] = {type, ownership0, ownership,backingivar};

    class_addProperty(targetClass, propertyName, attrs, 4);
}

void lg_printerProperty(Class targetClass){
    unsigned int outCount, i;
    objc_property_t *properties = class_copyPropertyList(targetClass, &outCount);
    for (i = 0; i < outCount; i++) {
        objc_property_t property = properties[i];
        fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));
    }
}

注意:添加成员变量class_addIvar一定要在注册objc_registerClassPair之前

void objc_registerClassPair(Class cls)
{
    ...
    // Clear "under construction" bit, set "done constructing" bit
    cls->ISA()->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING);
    cls->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING);

    // Add to named class table.
    addNamedClass(cls, cls->data()->ro->name);
}

// set and clear must not overlap
void changeInfo(uint32_t set, uint32_t clear) {
    assert(isFuture()  ||  isRealized());
    assert((set & clear) == 0);
    data()->changeFlags(set, clear);
}
BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *type)
{
    ...
    // Can only add ivars to in-construction classes.
    if (!(cls->data()->flags & RW_CONSTRUCTING)) {
        return NO;
    }

objc_registerClassPair注册之后,会把data()->flags改为RW_CONSTRUCTED,而class_addIvar只能在flagsRW_CONSTRUCTING时添加

Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
{
    ...
    // fixme mangle the name if it looks swift-y?
    objc_initializeClassPair_internal(superclass, name, cls, meta);

    return cls;
}
static void objc_initializeClassPair_internal(Class superclass, const char *name, Class cls, Class meta) {
   ...
    // Set basic info
    cls->data()->flags = RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING;
    meta->data()->flags = RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING;
}

objc_allocateClassPair动态创建类时,flags支持RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING4种状态,所以添加ivar一定要在注册之前,而方法 属性 协议是没这个限制求的,还有添加属性后,一定记得添加对应的setter getter方法,不然进行kvo赋值取值时会报错

四、一些相关API注释


/**
 *创建类对
 *superClass: 父类,传Nil会创建一个新的根类
 *name: 类名
 *extraBytes: 0
 *return:返回新类,创建失败返回Nil,如果类名已经存在,则创建失败
  objc_allocateClassPair(<#Class  _Nullable __unsafe_unretained superclass#>, <#const char * _Nonnull name#>, <#size_t extraBytes#>)
 */


/**
 *添加成员变量
 *cls 往哪个类添加
 *name 添加的名字
 *size 大小
 *alignment 对齐处理方式
 *types 签名
 *
 *这个函数只能在objc_allocateClassPair和objc_registerClassPair之间调用。不支持向现有类添加一个实例变量。
 *这个类不能是元类。不支持在元类中添加一个实例变量。
 *实例变量的最小对齐为1 << align。实例变量的最小对齐依赖于ivar的类型和机器架构。对于任何指针类型的变量,请通过log2(sizeof(pointer_type))。
  class_addIvar(<#Class  _Nullable __unsafe_unretained cls#>, <#const char * _Nonnull name#>, <#size_t size#>, <#uint8_t alignment#>, <#const char * _Nullable types#>)
 */

/**
 *往内存注册类
 * cls 要注册的类
 * objc_registerClassPair(<#Class  _Nonnull __unsafe_unretained cls#>)
 */


/**
 *往类里面添加方法
 *cls 要添加方法的类
 *sel 方法编号
 *imp 函数实现指针
 *types 签名
 *
 *class_addMethod(<#Class  _Nullable __unsafe_unretained cls#>, <#SEL  _Nonnull name#>, <#IMP  _Nonnull imp#>, <#const char * _Nullable types#>)
 */



/**
 *往类里面添加属性
 *cls 要添加属性的类
 *name 属性名字
 *attributes 属性的属性数组。
 *attriCount 属性中属性的数量。
 *
 *class_addProperty(<#Class  _Nullable __unsafe_unretained cls#>, <#const char * _Nonnull name#>, <#const objc_property_attribute_t * _Nullable attributes#>, <#unsigned int attributeCount#>)
 */

五、总结

  • 没实现load方法的分类编译时确定

  • 实现了load方法的分类运行时确定

  • 非懒加载分类 + 非懒加载类

    • 类的加载在 _read_images,分类的加载在类加载之后的reMethodizeClass
  • 非懒加载分类 + 懒加载类
    • 类的加载在 load_images内部的prepare_load_methods,分类的加载在类加载之后的 methodizeClass
  • 懒加载分类 + 非懒加载类

    • 类的加载在 _read_images,分类的加载在编译时
  • 懒加载分类 + 懒加载类

    • 类的加载在第一次消息发送的时候,分类的加载在编译时