上篇博文底层原理篇(三)我们讲到了类的编译、链接、加载的过程,接下来我们去探索一下类与分类、拓展与分类、关联对象等的内容!
1.类与分类
-
分类的结构
struct category_t {//仔细看一下,没有ivar的列表哦!!! 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; method_list_t *methodsForMeta(bool isMeta) { if (isMeta) return classMethods; else return instanceMethods; } property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi); };
这些属性,方法,是怎么和类产生关系的呢?下面我们来看看分类的加载!
我们要区分开类加载与分类加载的不同之处,这个地方很容易混淆!
- 这里需要说明一下,我们的分类为什么不能添加成员变量,这是因为分类结构中并没有成员变量列表的存储属性,分类在运行时被attach到类,是对rw的处理,在class_rw_t *rw中,并没有const ivar_list_t *ivars成员变量列表属性!!!这个是在class_ro_t *ro中处理的!所以我们不能在分类中添加成员变量,但是可以添加属性!!!
2.类拓展
延展类别又称为拓展(Extension),拓展是分类的一种特例,也叫匿名分类:写在.m中,没有名称!
还有一种写法:直接创建一个File type为Extension的OC文件!!也是我们的类拓展!
-
下面是两种实现的举例:
//1.在.m中 @interface WMPerson () //没有名字,我们的分类在这里的括号里面是有名称的 @property (nonatomic, copy) NSString *mName;//拓展的属性 - (void)extM_method;//拓展的方法(.m实现) @end //2.创建一个File type为Extension的OC文件WMPerson+WMExtension.h //其实和上面的一样,只是单独成为一个文件 @interface WMPerson () @property (nonatomic, copy) NSString *ext_name; @property (nonatomic, copy) NSString *ext_subject; - (void)extH_method;//在类的.m实现 @end
-
我们在read_images中看到分类的代码处理逻辑,但是我们没有看到拓展在哪里处理的?
-
那么我们该怎么去分析这个过程呢?
-
我们知道,在read_images中,类加载最初始:
//Discover classes. 读取所有的类信息 在这之后才会根据load方法初始化 for (EACH_HEADER) { classref_t *classlist = _getObjc2ClassList(hi, &count); if (! mustReadClasses(hi)) { // Image is sufficiently optimized that we need not call readClass() continue; } bool headerIsBundle = hi->isBundle(); bool headerIsPreoptimized = hi->isPreoptimized(); for (i = 0; i < count; i++) { Class cls = (Class)classlist[i]; Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized); const class_ro_t *ro = (const class_ro_t *)cls->data(); const char *cname = ro->name; const char *oname = "WMPerson"; if (cname && (strcmp(cname, oname) == 0)) { printf("_getObjc2ClassList 类名 :%s - %p\n",cname,cls); } if (newCls != cls && newCls) { // Class was moved but not deleted. Currently this occurs // only when the new class resolved a future class. // Non-lazily realize the class below. resolvedFutureClasses = (Class *)realloc(resolvedFutureClasses, (resolvedFutureClassCount+1) * sizeof(Class)); resolvedFutureClasses[resolvedFutureClassCount++] = newCls; } } }
-
我们断点断在printf("_getObjc2ClassList 类名 :%s - %p\n",cname,cls);打印处,如果我们的这个类加载了,我们可以去看看他在编译阶段的ro都存了什么!!!
//打印结果: (lldb) p ro (const class_ro_t *) $0 = 0x0000000100002320 (lldb) p *$0 (const class_ro_t) $1 = { flags = 388 instanceStart = 8 instanceSize = 40 reserved = 0 ivarLayout = 0x0000000100001eda "\x04" name = 0x0000000100001ed1 "WMPerson" baseMethodList = 0x0000000100002140 baseProtocols = 0x0000000000000000 ivars = 0x0000000100002250 weakIvarLayout = 0x0000000000000000 baseProperties = 0x00000001000022d8 _swiftMetadataInitializer_NEVER_USE = {} } (lldb) p $1.baseMethodList (method_list_t *const) $2 = 0x0000000100002140 (lldb) p *$2 (method_list_t) $3 = { entsize_list_tt<method_t, method_list_t, 3> = { entsizeAndFlags = 24 count = 11 first = { name = "extM_method" types = 0x0000000100001f84 "v16@0:8" imp = 0x0000000100001950 (objc-debug`-[WMPerson extM_method] at WMPerson.m:24) } } } (lldb) p $3.get(1) (method_t) $4 = { name = "extH_method" types = 0x0000000100001f84 "v16@0:8" imp = 0x0000000100001980 (objc-debug`-[WMPerson extH_method] at WMPerson.m:28) } (lldb) p $3.get(2) (method_t) $5 = { name = ".cxx_destruct" types = 0x0000000100001f84 "v16@0:8" imp = 0x0000000100001b70 (objc-debug`-[WMPerson .cxx_destruct] at WMPerson.m:18) } (lldb) p $3.get(3) (method_t) $6 = { name = "name" types = 0x0000000100001f8c "@16@0:8" imp = 0x00000001000019b0 (objc-debug`-[WMPerson name] at WMPerson.h:13) } (lldb) p $3.get(4) (method_t) $7 = { name = "setName:" types = 0x0000000100001f94 "v24@0:8@16" imp = 0x00000001000019e0 (objc-debug`-[WMPerson setName:] at WMPerson.h:13) } (lldb) p $3.get(5) (method_t) $8 = { name = "mName" types = 0x0000000100001f8c "@16@0:8" imp = 0x0000000100001a20 (objc-debug`-[WMPerson mName] at WMPerson.m:12) } (lldb) p $3.get(6) (method_t) $9 = { name = "setMName:" types = 0x0000000100001f94 "v24@0:8@16" imp = 0x0000000100001a50 (objc-debug`-[WMPerson setMName:] at WMPerson.m:12) } (lldb) p $3.get(7) (method_t) $10 = { name = "ext_name" types = 0x0000000100001f8c "@16@0:8" imp = 0x0000000100001a90 (objc-debug`-[WMPerson ext_name] at WMPerson+WMExtension.h:15) } (lldb) p $3.get(8) (method_t) $11 = { name = "setExt_name:" types = 0x0000000100001f94 "v24@0:8@16" imp = 0x0000000100001ac0 (objc-debug`-[WMPerson setExt_name:] at WMPerson+WMExtension.h:15) } (lldb) p $3.get(9) (method_t) $12 = { name = "ext_subject" types = 0x0000000100001f8c "@16@0:8" imp = 0x0000000100001b00 (objc-debug`-[WMPerson ext_subject] at WMPerson+WMExtension.h:16) } (lldb) p $3.get(10) (method_t) $13 = { name = "setExt_subject:" types = 0x0000000100001f94 "v24@0:8@16" imp = 0x0000000100001b30 (objc-debug`-[WMPerson setExt_subject:] at WMPerson+WMExtension.h:16) }
-
下标0和1分别是extM_method/extH_method两个方法,以及还有下面的一些setter/getter!
-
总结:这里我们看到,在加载这些类的时候,从ro中就能取出这些数据,说明是在编译期就处理好的了!!!
-
3.runtime关联对象
我们使用分类,很多时候会用到关联对象!
例如:我们在分类中,添加一个property属性,我们上面知道,分类的操作是对类中rw的操作,属性存储在property_list_t数组中!但是,在rw中并没有属性的setter/getter!所以我们要在分类的.m中对分类进行属性的关联操作!
//在.h中添加cate_name属性!
#import "WMPerson.h"
NS_ASSUME_NONNULL_BEGIN
@interface WMPerson (WMCategory)
@property (nonatomic, copy) NSString *cate_name;
@end
NS_ASSUME_NONNULL_END
//.m中对属性的setter/getter添加
#import "WMPerson+WMCategory.h"
#import <objc/runtime.h>
@implementation WMPerson (WMCategory)
-(void)setCate_name:(NSString *)cate_name {
//关联属性
objc_setAssociatedObject(self, @"name",cate_name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSString *)cate_name{
return objc_getAssociatedObject(self, @"name");
}
@end
-
下面我们来看这个关联对象的底层原理解析:
- 1.objc_setAssociatedObject()
- 1.上面如果不够清楚,我们来画个图:
- 2.总结:上图中,总表中包含了ObjectAssociationMap0、ObjectAssociationMap1等这样的表,这些表的下标是根据对应的类的地址经过处理得到的下标!根据下标object,迭代器遍历获取object对应的ObjectAssociationMap小表,再根据key,迭代器遍历ObjectAssociationMap小表,获取到key对应的ObjcAssociation对象!这样我们就找到了关联对象!!!
- 1.上面如果不够清楚,我们来画个图:
- 2.objc_getAssociatedObject()
总结:取值的查找过程和存值基本一样!
关联对象的释放是在调用dealloc过程中进行判断bool assoc = obj->hasAssociatedObjects();如果assoc为YES,则说明有关联对象,就会进行释放:if (assoc) _object_remove_assocations(obj);
- 1.objc_setAssociatedObject()
以上就是分类,拓展以及关联对象的一些底层知识,谢谢观赏!!!