一、Class的结构

  • 通过查看源码, 可以得出Class的底层结构如下图
  • 一开始class_data_bits_t bits;指向ro, 在加载的过程中创建了rw, 此时的指向顺序是bits->rw->ro

二、class_rw_t

  • class_rw_t里面的methodspropertiesprotocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容

三、class_ro_t

  • class_ro_t里面的baseMethodListbaseProtocolsivarsbaseProperties是一维数组,是只读的,包含了类的初始内容

四、method_t

  • method_t是对方法\函数的封装, method_t结构如下
  • IMP代表函数的具体实现
  • SEL代表方法\函数名,一般叫做选择器,底层结构跟char *类似

    • 可以通过@selector()sel_registerName()获得
    • 可以通过sel_getName()NSStringFromSelector()转成字符串
    • 不同类中相同名字的方法,所对应的方法选择器是相同的
  • types包含了函数返回值、参数编码的字符串
  • iOS中提供了一个叫做@encode的指令,可以将具体的类型表示成字符串编码

五、方法缓存

  • Class内部结构中有个方法缓存(cache_t),用散列表哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度
  • 调用方法时的查找过程
以对象方法为例:
通过isa找到class
先会先从cache中查找, 如果有方法, 直接调用
如果没有方法, 在自身的class->bits->rw中查找方法
如果找到方法直接调动,并将方法缓存到cache中
如果没有找到, 会通过superclass找到父类, 从父类->bits->rw中查找方法
如果在父类中找到方法, 就直接调用, 同时将方法存到自己(不是父类的)的cache中, 如果一直找不到, 就进入下一个阶段
复制代码

散列表存储方法

  • 一开始, 系统会分配给cache_t->_buckets一段内存, 假设第一次分配了足够存储3个方法的内存
  • 此时cache_tmask等于2, 即_buckets的长度 - 1
  • 当存储方法时, 会用SEL & mask, 获取到一个数字, 用这个数字做为索引, 将该方法存储到_buckets
  • 假如有以下代码, Person继承自NSObject, 有三个方法eatrunsay
  • Student继承自Person, 有三个方法learningexercisesdancing
  • 当一个Student实例调用learning方法时, 就会用@selector(learning) & _buckets.mask来获取存储的索引,然后将learning方法存放到Student类对象cache
  • 假设获取到的索引值为0, 那么散列表的结构类似下图
  • 如果Student实例调用父类Personeat方法时, 根据@selector(eat) & _buckets.mask的结果将becket_t插入到相应位置
  • 假设@selector(eat) & _buckets.mask结果是2, 那么散列表结构类似下图
  • 即使eatPerson中的方法, 但是Student调用,也会存到Studentcache

  • 当多个数都&一个固定值时, 那么肯定就有重复的可能出现, 比如

 1001 1010          1000 1011
&0000 1010         &0000 1010
-----------       ------------0000 1010          0000 1010
复制代码
  • 所以通过多个SEL & _buckets.mask获取到的值就有可能是重复的
  • 此时,存储方法的索引就会-1, 然后在查找-1后所在位置是否空缺, 如果有空缺就会存储
  • 如果Student实例调用exercises方法, 会计算@selector(exercises) & _buckets.mask的结果作为索引值
  • 假设@selector(exercises) & _buckets.mask的结果是2, 此时因为索引2的位置已经存储eat方法, 所以索引会-1变成1, 然后查看1的位置是否空缺, 如果空缺就会存储, 如下图
  • 如果Student实例继续调用dancing方法, 此时cache->buckets已经存储满
  • 那么buckets的容量会扩大一倍, 容量变为6,重新计算cache->mask的值为5, 接着清空buckets中之前缓存的所有方法
  • 然后计算@selector(dancing) & _buckets.mask的值, 如果此时的结果是3, 那么散列表的结构类似下图
  • 因为已经清空过cache中的所有方法, 所以此时只存储dancing方法

六、使用代码验证cache存储流程

准备代码

  • Person继承自NSObject, 有一个对象方法personTest
  • Student继承自Person, 有一个对象方法studentTest
  • GoodStudent继承自Student, 有一个对象方法goodStudentTest

方法调用后, 会缓存在cache中

  • main.mm文件中有如下代码, 并在1921行打断点
  • 执行程序, 查看personClass的结构吗可以看到cache中的_buckets长度是_mask + 1 = 4, 此时已经存储一个方法, 即刚调用过的init方法
  • 接下来过掉第一个断点, 可以看到personTest已经被存到了_buckets
  • 下面使用代码查看更复杂的存储方式, 首先有如下代码, 在19, 20, 21, 23四处打上断点
  • 执行程序, 可以看到此时_buckets中只存了一个方法
  • 过掉19行断点, 可以看到有一个方法被存到_buckets中, 这个方法就是goodStudentTest
  • 过掉20行断点, 可以看到又一个方法被存到_buckets中, 这个方法就是studentTest, 此时_buckets中已经存放了三个方法
  • 接着过掉21行断点, 此时_buckets进行了扩容,从新计算了mask, 同时清空了缓存的方法, 并将新调用的personTest存到了_buckets

打印cache中存储的方法

  • main函数中的代码修改为如下所示
  • 运行程序, 可以看到cache->_buckets中存储的方法, 其中包含了initgoodStudentTeststudentTest
  • 在将代码修改为如下所示, goodStudent先调用5个方法, 在打印缓存的方法列表
  • 执行程序, 可以看到下面的打印
  • 在上面已经知道, 在执行[goodStudent personTest];时已经进行了扩容, 所以此时的容量是8, 由于清空了之前缓存的方法, 所以现在_buckets中只存储了三个方法

通过索引获取方法

  • main函数中的代码修改为如下所示
  • 执行代码, 可以看到打印信息, 已经将personTest的方法和地址打印出来
  • 我们可以通过控制台来验证打印的确实就是personTest方法
  • 我们可以用同样的方式, 打印这三个方法
  • 执行程序, 可以看到三个方法地址的打印
  • 但是可以看到personTestgoodStudentTest取出的地址是相同的
  • 我们可以明确的知道这两个方法不同, 那么取出地址相同的原因就是SEL & mask得到的索引值相同
  • 通过打印, 也可以看到索引值是相同的
  • 之前已经说过, 不同的数字&一个固定的数, 肯定会出现重复的情况, 所以取出的方法也就会是同一个
  • 此时取方法, 需要将索引-1然后再去取出方法, 如果取出的是空或者方法名不对, 那么就继续-1, 再去取方法, 以此类推
  • 索引我们通过下面的方式, 就能取出缓存中正确的方法
  • 执行程序, 可以看到取出的方法全部正确
  • MJClassInfo.h文件中代码如下
#import <Foundation/Foundation.h>#ifndef MJClassInfo_h
#define MJClassInfo_h# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
# endif#if __LP64__
typedef uint32_t mask_t;
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t cache_key_t;#if __arm__  ||  __x86_64__  ||  __i386__
// objc_msgSend has few registers available.
// Cache scan increments and wraps at special end-marking bucket.
#define CACHE_END_MARKER 1
static inline mask_t cache_next(mask_t i, mask_t mask) {return (i+1) & mask;
}#elif __arm64__
// objc_msgSend has lots of registers available.
// Cache scan decrements. No end marker needed.
#define CACHE_END_MARKER 0
static inline mask_t cache_next(mask_t i, mask_t mask) {return i ? i-1 : mask;
}#else
#error unknown architecture
#endifstruct bucket_t {cache_key_t _key;IMP _imp;
};struct cache_t {bucket_t *_buckets;mask_t _mask;mask_t _occupied;IMP imp(SEL selector){mask_t begin = _mask & (long long)selector;mask_t i = begin;do {if (_buckets[i]._key == 0  ||  _buckets[i]._key == (long long)selector) {return _buckets[i]._imp;}} while ((i = cache_next(i, _mask)) != begin);return NULL;}
};struct entsize_list_tt {uint32_t entsizeAndFlags;uint32_t count;
};struct method_t {SEL name;const char *types;IMP imp;
};struct method_list_t : entsize_list_tt {method_t first;
};struct ivar_t {int32_t *offset;const char *name;const char *type;uint32_t alignment_raw;uint32_t size;
};struct ivar_list_t : entsize_list_tt {ivar_t first;
};struct property_t {const char *name;const char *attributes;
};struct property_list_t : entsize_list_tt {property_t first;
};struct chained_property_list {chained_property_list *next;uint32_t count;property_t list[0];
};typedef uintptr_t protocol_ref_t;
struct protocol_list_t {uintptr_t count;protocol_ref_t list[0];
};struct class_ro_t {uint32_t flags;uint32_t instanceStart;uint32_t instanceSize;  // instance对象占用的内存空间
#ifdef __LP64__uint32_t reserved;
#endifconst uint8_t * ivarLayout;const char * name;  // 类名method_list_t * baseMethodList;protocol_list_t * baseProtocols;const ivar_list_t * ivars;  // 成员变量列表const uint8_t * weakIvarLayout;property_list_t *baseProperties;
};struct class_rw_t {uint32_t flags;uint32_t version;const class_ro_t *ro;method_list_t * methods;    // 方法列表property_list_t *properties;    // 属性列表const protocol_list_t * protocols;  // 协议列表Class firstSubclass;Class nextSiblingClass;char *demangledName;
};#define FAST_DATA_MASK          0x00007ffffffffff8UL
struct class_data_bits_t {uintptr_t bits;
public:class_rw_t* data() {return (class_rw_t *)(bits & FAST_DATA_MASK);}
};/* OC对象 */
struct mj_objc_object {void *isa;
};/* 类对象 */
struct mj_objc_class : mj_objc_object {Class superclass;cache_t cache;class_data_bits_t bits;
public:class_rw_t* data() {return bits.data();}mj_objc_class* metaClass() {return (mj_objc_class *)((long long)isa & ISA_MASK);}
};#endif /* MJClassInfo_h */
复制代码

转载于:https://juejin.im/post/5c84a096e51d4539d9564f4e

小码哥iOS学习笔记第十二天:Class结构相关推荐

  1. 小码哥iOS学习笔记第二天: OC对象的分类

    Objective-C中的对象, 简称OC对象, 主要可以分为3种 instance对象(实例对象) class对象(类对象) meta-class对象(元类对象) 一.instance instan ...

  2. 小码哥iOS学习笔记第八天: block的底层结构

    一.最简单的block 1.最简单的block结构 ^{NSLog(@"this is a block");NSLog(@"this is a block"); ...

  3. 小码哥iOS拓展班2期

    小码哥iOS拓展班2期,视频全套不加密,有FM,直播,RAC,汇编项目,源码文档齐全. 注: 视频分为破解版和重录版,破解版码哥课堂和直播项目不全:重录版所有的视频和文档都是齐全的,只有直播项目第一天 ...

  4. Vue(小码哥王洪元)笔记07路由案例tabbar

    1.tabbar案例01 进行了简单的布局 1.创建一个样式文件base.css body{margin:0; padding: 0;} 2.修改App.vue <template>< ...

  5. Vue(小码哥王洪元)笔记06路由,url的hash,history,router-linke,路由跳转,动态路由,懒加载,路由嵌套,router参数传递,导航守卫

    1.什么是路由 路由器提供了两种机制:路由和传送 路由:数据报从来源到目的地的路径 传输:将输入端的数据转移到合适的输出端 路由有一个非常重要的概念教路由表 路由表本质上就是一个映射表,决定了数据包的 ...

  6. iOS-多线程-(小码哥底层原理笔记)

    iOS中常见的多线程方案 GCD的常用函数 同步方式执行任务 dispatch_sync(dispatch_queue_t _Nonnull queue, ^(void)block) queue - ...

  7. 小码哥IOS大神班11期 大神班

    链接:http://pan.baidu.com/s/1eRTq8oE 密码:mm8a 转载于:https://www.cnblogs.com/aleafo/p/10622806.html

  8. 最新李明杰小码哥ios开发视频教程四期

  9. IOS 学习笔记 2015-03-24 OC-API-常用结构体

    一 标题 常用结构体二 API 1 NSRange 表示一个范围 A 实例化 NSRange rg={3,5};//第一参数是起始位置第二个参数是长度B 实例化 NSRange rg2=NSMakeR ...

最新文章

  1. 6.字符串解析(LeetCode第394题)
  2. C#和SqlServer中处理时间格式问题
  3. 牛客网(剑指offer) 第十二题 数值的整数次方
  4. Python 爬虫知识点 - 淘宝商品检索结果抓包分析(续一)
  5. IE8采用IE7模式
  6. linux 粘贴网站地址,linux 复制粘贴
  7. DeleteCommand属性---删除数据集指定的行保存到数据源中
  8. ipad能安装python么_ipad上能安装python吗
  9. mysql面试常问 1: 谈谈MySQL表级锁和行级锁
  10. mysql 5.5.50_mysql 5.5.50 乱码解决
  11. 挤爆了!故宫首次晚间开放:预约票平台一度502
  12. Could not connect to SMTP host: smtp.163.com, port: 25;阿里云 ECS
  13. Nexus6P 设置Debug模式
  14. 【Hadoop】HDFS三组件:NameNode、SecondaryNameNode和DataNode
  15. DEBUG模式下,视频丢包严重;RELEASE就好了
  16. FastDFS实现原理及流程
  17. 金蝶K3CLOUD7.2内部培训PPT下载
  18. 用Python3创建httpServer
  19. [转]一个中高级PHP工程师所应该具备的能力
  20. HMACSHA1 加密算法

热门文章

  1. P4995 跳跳!【贪心】
  2. ES系列8-ElasticSearch搜索及持久化变更
  3. 固态继电器和电磁继电器模块
  4. 文件加密—巧用闪灵文件夹锁隐藏视频
  5. 南京信息工程大学宿舍管理员吴阿姨的精彩演讲
  6. 第二节 概率、古典概型、几何概型
  7. 手把手教你用 SQL 实现电商产品用户分析
  8. 6.初次见面的礼貌用语
  9. ACM--steps--dyx--5.1.2--小希的迷宫
  10. 《Contributing to Eclipse中文版》书评摘录