iOS底层原理篇(四)----拓展、分类、关联对象

上篇博文底层原理篇(三)我们讲到了类的编译、链接、加载的过程,接下来我们去探索一下类与分类、拓展与分类、关联对象等的内容!

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()
      objc_setAssociatedObject分析
      • 1.上面如果不够清楚,我们来画个图:
        在这里插入图片描述
      • 2.总结:上图中,总表中包含了ObjectAssociationMap0、ObjectAssociationMap1等这样的表,这些表的下标是根据对应的类的地址经过处理得到的下标!根据下标object,迭代器遍历获取object对应的ObjectAssociationMap小表,再根据key,迭代器遍历ObjectAssociationMap小表,获取到key对应的ObjcAssociation对象!这样我们就找到了关联对象!!!
    • 2.objc_getAssociatedObject()
      objc_getAssociatedObject()分析 总结:取值的查找过程和存值基本一样!
      关联对象的释放是在调用dealloc过程中进行判断bool assoc = obj->hasAssociatedObjects();如果assoc为YES,则说明有关联对象,就会进行释放:if (assoc) _object_remove_assocations(obj);

以上就是分类,拓展以及关联对象的一些底层知识,谢谢观赏!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值