上篇文章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中情况了
为了更好的跟中研究的分类,分别在添加分类的相关方法addUnattachedCategoryForClass、attachCategories、methodizeClass等中添加相关的打印:
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.非懒加载分类 + 非懒加载类

realizeClassWithoutSwift里面会递归父类和元类
supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));
_read_imagesrealizeClassWithoutSwiftmethodlizeClassaddUnattachedCategoryForClassremethodizeClass实现类信息attachCategories加载分类数据进来`
2.非懒加载分类 + 懒加载类

prepare_load_methods中
_read_imagesaddUnattachedCategoryForClassprepare_load_methods提前到这里加载类信息realizeClassWithoutSwiftmethodlizeClassattachCategories
3.懒加载分类 + 非懒加载类

_read_imagesrealizeClassWithoutSwiftmethodlizeClass- 直接在相应
data->ro不进addUnattachedCategoryForClass
4.懒加载分类 + 懒加载类


都是懒加载的,所以类是在第一次发送消息时才加载
lookUpImpOrForward消息发送的时候realizeClassWithoutSwiftmethodlizeClass- 直接在相应
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只能在flags为RW_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,分类的加载在编译时
- 类的加载在
-
懒加载分类 + 懒加载类
- 类的加载在第一次消息发送的时候,分类的加载在编译时