iOS分类底层实现原理小记

时间:2022-02-11 15:39:34
    • 摘要:iOS分类底层是怎么实现的?本文将分如下四个模块进行探究分类的结构体编译时的分类分类的加载总结本文使用的runtime源码版本是objc4-680文中类与分类代码如下//类@interfacePerson:NSObject@property(nonatomic,copy)NSString*presonName;@end@implementationPerson-(void)doSomeThing{NSLog(@"Person");}@end//分类@int
    • iOS 分类底层是怎么实现的?
      本文将分如下四个模块进行探究

      分类的结构体 
      编译时的分类 
      分类的加载 
      总结

      本文使用的runtime源码版本是 objc4 - 680
      文中类与分类代码如下

      //类
      @interface Person : NSObject
      @property (nonatomic ,copy) NSString *presonName;
      @end
      @implementation Person
      - (void)doSomeThing{
      NSLog(@"Person");
      }
      @end 
      // 分类
      @interface Person(categoryPerson)
      @property (nonatomic ,copy) NSString *categoryPersonName;
      @end
      @implementation Person(categoryPerson)
      - (void)doSomeThing{
      NSLog(@"categoryPerson");
      }
      @end 
      1.分类的结构体 
      struct _category_t {
      const char *name;//类名
      struct _class_t *cls;//类
      const struct _method_list_t *instance_methods;//category中所有给类添加的实例方法的列表(instanceMethods)
      const struct _method_list_t *class_methods;//category中所有添加的类方法的列表(classMethods)
      const struct _protocol_list_t *protocols;//category实现的所有协议的列表(protocols)
      const struct _prop_list_t *properties;//category中添加的所有属性(instanceProperties)
      };
      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; // 属性列表
      // 如果是元类,就返回类方法列表;否则返回实例方法列表
      method_list_t *methodsForMeta(bool isMeta) {
      if (isMeta) {
      return classMethods;
      } else {
      return instanceMethods;
      }
      }
      // 如果是元类,就返回 nil,因为元类没有属性;否则返回实例属性列表,但是...实例属性
      property_list_t *propertiesForMeta(bool isMeta) {
      if (isMeta) {
      return nil; // classProperties;
      } else {
      return instanceProperties;
      }
      }
      }; 
      2.编译时的分类 
      2.1分类的属性 
      // Person(categoryPerson) 属性列表
      static struct /*_prop_list_t*/ {
      unsigned int entsize; // sizeof(struct _prop_t)
      unsigned int count_of_properties;
      struct _prop_t prop_list[1];
      } _OBJC_$_PROP_LIST_Person_$_categoryPerson __attribute__ ((used, section ("__DATA,__objc_const"))) = {
      sizeof(_prop_t),
      1,
      {{"categoryPersonName","aliyunzixun@xxx.com/"NSString/",C,N"}}
      };
      // Person 属性列表
      static struct /*_prop_list_t*/ {
      unsigned int entsize; // sizeof(struct _prop_t)
      unsigned int count_of_properties;
      struct _prop_t prop_list[1];
      } _OBJC_$_PROP_LIST_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
      sizeof(_prop_t),
      1,
      {{"presonName","aliyunzixun@xxx.com/"NSString/",C,N,V_presonName"}}
      };

      对比上述代码可以发现:在分类中可以声明属性,并且同样会生成一个 _prop_list_t 的结构体

      2.2分类的实例变量? 
      // Person 实例变量列表
      static struct /*_ivar_list_t*/ {
      unsigned int entsize; // sizeof(struct _prop_t)
      unsigned int count;
      struct _ivar_t ivar_list[1];
      } _OBJC_$_INSTANCE_VARIABLES_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
      sizeof(_ivar_t),
      1,
      {{(unsigned long int *)&;OBJC_IVAR_$_Person$_presonName, "_presonName", "@/"NSString/"", 3, 8}}
      };

      因为 _category_t 这个结构体中并没有 _ivar_list_t
      所以在编译时系统没有Person(categoryPerson) 没有生成类似的相应结构体,也没有生成 _categoryPersonName。

      2.3分类的实例方法 
      // Person 实例方法结构体
      static struct /*_method_list_t*/ {
      unsigned int entsize; // sizeof(struct _objc_method)
      unsigned int method_count;
      struct _objc_method method_list[3];
      } _OBJC_$_INSTANCE_METHODS_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
      sizeof(_objc_method),
      3,
      {{(struct objc_selector *)"doSomeThing", "aliyunzixun@xxx.com:8", (void *)_I_Person_doSomeThing},
      {(struct objc_selector *)"presonName", "@aliyunzixun@xxx.com:8", (void *)_I_Person_presonName},
      {(struct objc_selector *)"setPresonName:", "aliyunzixun@xxx.com:aliyunzixun@xxx.com", (void *)_I_Person_setPresonName_}}
      };
      // Person(categoryPerson )实例方法结构体
      static struct /*_method_list_t*/ {
      unsigned int entsize; // sizeof(struct _objc_method)
      unsigned int method_count;
      struct _objc_method method_list[1];
      } _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_categoryPerson __attribute__ ((used, section ("__DATA,__objc_const"))) = {
      sizeof(_objc_method),
      1,
      {{(struct objc_selector *)"doSomeThing", "aliyunzixun@xxx.com:8", (void *)_I_Person_categoryPerson_doSomeThing}}
      };

      对比上述方法可以看到:虽然分类可以声明属性,但是编译时,系统并没有生成分类属性的 get/set 方法,所以,这就是为什么分类要利用
      runtime 动态添加属性,如何动态添加属性,有兴趣的同学可以查看下面文章 iOS分类中通过runtime添加动态属性

      2.4分类的结构体 
      // Person(categoryPerson ) 结构体
      static struct _category_t _OBJC_$_CATEGORY_Person_$_categoryPerson __attribute__ ((used, section ("__DATA,__objc_const"))) = 
      {
      "Person",
      0, // &;OBJC_CLASS_$_Person,
      (const struct _method_list_t *)&;_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_categoryPerson,
      0,
      0,
      (const struct _prop_list_t *)&;_OBJC_$_PROP_LIST_Person_$_categoryPerson,
      };

      这是系统在编译时实例化 _category_t 生成的
      _OBJC_$_CATEGORY_Person_$_categoryPerson

      2.5分类数组 
      static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
      &;_OBJC_$_CATEGORY_Person_$_categoryPerson,
      };

      编译器最后生成了一个数组,数组的元素就是我们创建的各个分类,用来在运行时加载分类。

      3.分类的加载 
      3.1加载分类调用栈 
      _objc_init
      └──map_2_images
      └──map_images_nolock
      └──_read_images

      分类加载的调用栈如上述

      _objc_init 算是整个 objc4 的入口,进行了一些初始化操作,注册了镜像状态改变时的回调函数 
      map_2_images 主要是加锁并调用 map_images_nolock

      map_images_nolock 在这个函数中,完成所有 class 的注册、fixup等工作,还有初始化自动释放池、初始化 side table 等工作并在函数后端调用了 _read_images

      _read_images 方法干了很多苦力活,比如加载类、Protocol、Category,加载分类的代码就写在 _read_images 函数的尾部

      该调用栈入口函数 void _objc_init(void) 在 objc-os.mm 中,有兴趣的同学可以去看看这些函数里都做了什么

      3.2 _read_images 中加载分类的源码

      加载分类的源码主要做了两件事

      把category的实例方法、协议以及属性添加到类上 
      把category的类方法和协议添加到类的metaclass上

      略去与本文无关的代码,得到如下代码

      // 获取 镜像中的所有分类
      category_t **catlist = _getObjc2CategoryList(hi, &;count);
      // 遍历 catlist
      for (i = 0; i < count; i++) {
      category_t *cat = catlist[i];
      Class cls = remapClass(cat->cls);
      if (cat->instanceMethods || cat->protocols
      || cat->instanceProperties) 
      {
      addUnattachedCategoryForClass(cat, cls, hi);
      if (cls->isRealized()) {
      remethodizeClass(cls);
      classExists = YES; 
      }
      }
      if (cat->classMethods || cat->protocols
      /* || cat->classProperties */)
      {
      addUnattachedCategoryForClass(cat, cls->ISA(), hi);
      if (cls->ISA()->isRealized()) {
      remethodizeClass(cls->ISA());
      }
      }
      }

      做上述事情主要用到是如下两个函数

      addUnattachedCategoryForClass(cat, cls, hi) 为类添加未依附的分类
      执行过程伪代码:
      1.取得存储所有 unattached 分类的列表

      NXMapTable *cats = unattachedCategories();

      2.从 cats 列表中找倒 cls 对应的 unattached 分类的列表

      category_list *list = (category_list *)NXMapGet(cats, cls);

      3.将新来的分类 cat 添加刚刚开辟的位置上

      list->list[list->count++] = (locstamped_category_t){cat, catHeader};

      4.将新的 list 重新插入 cats 中,会覆盖老的 list

      NXMapInsert(cats, cls, list);

      执行完上述过程后,系统将这个分类放到了一个 cls 对应的 unattached 分类的 list 中(有点绕口....),这个 list 会在 remethodizeClass(cls) 方法用到

      remethodizeClass(cls)
      执行过程伪代码:
      1.取得 cls 类的 unattached 的分类列表

      category_list *cats = unattachedCategoriesForClass(cls, false/*not realizing*/)

      2.将 unattached 的分类列表 attach 到 cls 类上

      attachCategories(cls, cats, true /* 清空方法缓存 flush caches*/);

      执行完上述过程后,系统就把category的实例方法、协议以及属性添加到类上

      最后再来看一下
      attachCategories(cls, cats, true /* 清空方法缓存 flush caches*/)内部的实现过程
      1.在堆上创建方法、属性、协议数组,用来存储分类的方法、属性、协议

      method_list_t **mlists = (method_list_t **)malloc(cats->count * sizeof(*mlists));
      property_list_t **proplists = (property_list_t **)malloc(cats->count * sizeof(*proplists));
      protocol_list_t **protolists = (protocol_list_t **)malloc(cats->count * sizeof(*protolists));

      2.遍历 cats ,取出各个分类的方法、属性、协议,并填充到上述代码创建的数组中

      int mcount = 0; // 记录方法的数量
      int propcount = 0; // 记录属性的数量
      int protocount = 0; // 记录协议的数量
      int i = cats->count; // 从后开始,保证先取最新的分类
      bool fromBundle = NO; // 记录是否是从 bundle 中取的
      while (i--) { // 从后往前遍历
      auto&; entry = cats->list[i]; // 分类,locstamped_category_t 类型
      // 取出分类中的方法列表;如果是元类,取得的是类方法列表;否则取得的是实例方法列表
      method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
      if (mlist) {
      mlists[mcount++] = mlist; // 将方法列表放入 mlists 方法列表数组中
      fromBundle |= entry.hi->isBundle(); // 分类的头部信息中存储了是否是 bundle,将其记住
      }
      // 取出分类中的属性列表,如果是元类,取得是nil
      property_list_t *proplist = entry.cat->propertiesForMeta(isMeta);
      if (proplist) {
      proplists[propcount++] = proplist; // 将属性列表放入 proplists 属性列表数组中
      }
      // 取出分类中遵循的协议列表
      protocol_list_t *protolist = entry.cat->protocols;
      if (protolist) {
      protolists[protocount++] = protolist; // 将协议列表放入 protolists 协议列表数组中
      }
      }

      3.取出 cls 的 class_rw_t 数据

      auto rw = cls->data();

      4.存储方法、属性、协议数组到 rw

      // 准备 mlists 中的方法
      prepareMethodLists(cls, mlists, mcount/*方法列表的数量*/, NO/*不是基本方法*/, fromBundle/*是否来自bundle*/);
      // 将新属性列表添加到 rw 中的属性列表数组中
      rw->properties.attachLists(proplists, propcount);
      // 释放 proplists
      free(proplists); // 释放 proplists
      // 将新协议列表添加到 rw 中的协议列表数组中
      rw->protocols.attachLists(protolists, protocount);
      // 释放 protolists
      free(protolists); // 释放 protolists
      // 将新协议列表添加到 rw 中的协议列表数组中
      rw->protocols.attachLists(protolists, protocount);
      // 释放 protolists
      free(protolists);

      4.总结

      至此,本文接近尾声
      希望本文的读者能够了解到

      分类的结构体 
      分类中是否能添加属性 
      分类中是否有实例变量 
      分类是如何 attach 到类上的

      行笔简陋,如有问题,敬请指正

    • 以上是

iOS分类底层实现原理小记

      的内容,更多

小记底层原理实现分类iOS

      的内容,请您使用右上方搜索功能获取相关信息。