iOS 底层探索篇 —— 类和分类的加载

256 阅读4分钟

前言

类的动态加载

类的动态加载实际上就是运用runtime运行时来加载一个类。

动态创建例子

下面贴上一个动态创建的方式

一个类的动态创建、为类增加成员变量就是这样的一个流程,下面对每段API进行分析

这里注意一下 如果第2步和第3步调换一下位置,并且还调用`setValue:forKey:`方法会出现奔溃,将会在下面分析。

动态创建API分析

1. objc_allocateClassPair

创建类

objc_allocateClassPair(<#Class  _Nullable __unsafe_unretained superclass#>, <#const char * _Nonnull name#>, <#size_t extraBytes#>)
  • 参数1 superclass 创建类的父类。
  • 参数2 name 创建类的名字。
  • 参数3 extraBytes 提供类的内存字节,可以为0

2. class_addIvar

为类增加成员变量

class_addIvar(<#Class  _Nullable __unsafe_unretained cls#>, <#const char * _Nonnull name#>, <#size_t size#>, <#uint8_t alignment#>, <#const char * _Nullable types#>)
  • 参数1 cls 动态创建的类。
  • 参数2 name 成员变量的名字。
  • 参数3 size 该成员变量的字节大小,可以直接用sizeof(NSString *)来获取。
  • 参数5 types 该成员变量的类型。
  • 参数4 alignment 这个参数我们不知道怎么填,可以深入API里面去查看看。
    我们知道NSString的字节大小为8,通过log2()函数可以获取到该值,并且对应到API里面1<<alignment即为我们获取到的值。

3. objc_registerClassPair()

注册类到内存

动态创建顺序

  1. class_addIvar()函数里面

如果类的rwflags标志了已经构造 就直接返回NO

  1. objc_registerClassPair

一旦类开始注册,就会更改类的构造信息,标志已经构造。

综合上面的两个函数分析,可以知道如果类注册之后就更改相应的构造标志信息,如果再去增加成员变量是无法增加的。

动态创建增加属性

1. 增加属性

Class cls = objc_allocateClassPair([NSObject class], "XDStudent", 0);
  
objc_registerClassPair(cls);

xd_class_addProperty(cls, "name");

id student = [cls alloc];

[student setValue:@"123HH" forKey:@"name"];
NSLog(@"%@",[student valueForKey:@"name"]);
void xd_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);
    
}

这里面是封装的一个增加属性函数,主要是objc_property_attribute_t会比较复杂一点。

@property (nonatomic, copy) NSString *nickName;

我们可以看上面的这段代码,

  • nickName是某个对象的属性,但是属性也有自己的属性,比如nonatomiccopyNSString
  • 在上面处理objc_property_attribute_t的时候,有固定的写法,typebackingivar这两个变量。有通过属性的属性来对应不同的写法,比如ownership0ownership这两个变量。

运行分析

增加属性之后,直接`setValue:forKey:`运行之后会出现奔溃的情况,报错的原因是没有找到`set`方法。

我们知道属性在编译阶段会自动生成成员变量,setter,getter方法,但是这里我们只是动态的增加了属性,并没有setter方法,所有会报错。接下来增加方法

2. 增加方法

Class cls = objc_allocateClassPair([NSObject class], "XDStudent", 0);

objc_registerClassPair(cls);

xd_class_addProperty(cls, "name");

class_addMethod(cls, @selector(setName:), (IMP)xd_setName, "v@:@");

class_addMethod(cls, @selector(name), (IMP)xd_name, "@@:");

id student = [cls alloc];

[student setValue:@"123HH" forKey:@"name"];

NSLog(@"%@",[student valueForKey:@"name"]);

为动态类增加settergetter方法,并且给定相应的IMP

动态加载总结

  1. 这里就是涉及到了ro与rw的关系。
  2. 成员变量在ro结构体里面,命名为只读类型,则需要在类注册到内存之前就完成相应的赋值操作。
  3. 属性、方法在类注册到内存之后,还有一个赋值在rw里面,命名为读写类型。

类和分类的加载

不管是类还是分类都区分懒加载和非懒加载,主要的区别就是有没有实现load方法,接下来我们开始组合分析。

懒加载类 + 懒加载分类

懒加载类 + 非懒加载分类

非懒加载类 + 懒加载分类

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

学习之路,砥砺前行

不足之处可以在评论区指出