前言

1 - OC机制很多都是基于 Runtime实现的,比如指针的弱引用。OC的消息机制属于 Runtime的一部分

2 - OC是一门动态语言,在程序运行过程中就可以修改已经编译好的代码

3 - Runtime API基本上是开源的,由 C、C++甚至汇编语言编写的

isa

1 - arm64下的 isa结构

2 - 代码示例:对 Person进行弱引用并关联对象

 1 #import <Foundation/Foundation.h>
2 #import "Person.h"
3 #import <objc/runtime.h>
4 int main(int argc, const char * argv[]) {
5
6 @autoreleasepool {
7
8 Person *person = [[Person alloc] init];
9
10 // 弱引用
11 __weak Person *weakPerson = person;
12 // 关联对象
13 objc_setAssociatedObject(person, @"name", @"rose", OBJC_ASSOCIATION_COPY_NONATOMIC);
14
15 // p/x person->isa
16 // 0x5A100100F1B
17 }
18
19 return 0;
20
21 }

转换成二进制

Class

1 - Class结构

2 - class_rw_t和 class_ro_t中都存在类名、方法列表、属性列表等。有何不同 ?

① class_rw_t里面的 methods、properties、protocols是二维数组,且可读可写。它包含了类的初始内容、分类的内容,下面以方法列表为例:方法列表1 和方法列表2 中存储的是分类方法;那么方法列表3 中存储的就是类的初始方法

② class_ro_t里面的 baseMethodList、baseProtocols、ivars、baseProperties 是一维数组,且只读。包含了类的初始内容。下面以方法列表为例

注:一个类在初始化时只有 class_ro_t,而 bits也原本指向 class_ro_t。但是随着程序员对类的使用,例如增加方法、属性、分类等操作,class_ro_t最终会被并到 class_rw_t 里面,就是说 class_rw_t原本并不存在,是后来对类不断丰富扩展才产生的

③ method_t:是对方法、函数的封装

④ Type Encoding:@encode指令可以将具体的类型表示成字符串编码

// OC中的方法参数 id和 SEL会默认隐藏
-(void)test;
// 其实质如下
-(void)test:(id)self _cmd:(SEL)_cmd; // 方法的 encode编码
// " i24 @0 :8 i16 f20 "
// i 代表返回类型 int @ 代表 id 类型 : 代表 SEL f 代表 float 类型
// 最前面的 24代表内存大小,共计占据 24个字节
// 0 代表 id参数内存地址从 0开始,占据8个字节。依次类推
-(int)test:(int)age height:(float)height;

3 - 方法缓存 cache_t

① 它用散列表来缓存曾经调用过的方法,可以提高方法的查找速度。缓存方法以散列表的结构存在

一个 bucket_t就相当于一个方法

② iOS散列表原理

在首次使用方法时,系统会事先创建一个散列表(比如长度是 10):地址编号-内容

比如调用了 personTest和 personTest2两方法。系统会先把两方法分别同掩码进行位与运算,计算出的结果便是对应散列表的地址编号,将其放入表中。注:有的编程语言是对 _mask取余操作而位与运算,其实两者得出的结果终将小于散列表长度

当我们再次调用已使用过的方法时,比如 personTest 系统会先通过位运算 @selector(personTest)  & _mask == 4取得结果 4,然后根据结果(地址编号)直接从散列表中取出方法进行调用

那么问题来了:如果有新的方法在与上掩码后得出的结果对应散列表中内存空间已经被占用,比如 @selector(personTest4) & _mask == 4已被 personTest占用,怎么办 ?苹果给出的解决办法是把结果 -1,就是说最终会把 personTest4放进 3的位置......那么问题又又又来了:如果 3的位置也已经被占用,怎么办?继续 -1往上遍历 -->表头 -->表尾 --> 计算结果 +1(5),如此就闭环遍历了整个散列表

最终当我们 -1操作直至遍历了整个列表都没有找到空闲内存可用,那么系统就会把散列表扩容(原散列表长度*2)。散列表扩容的同时会把原表中的内容全部清空,原来的方法缓存不复存在,是一份崭新的散列表

③ 方法缓存原理

    当给一个对象发送消息,首先在 objc_class中的 cache里查询

如果缓存中没有该方法,就在 class_rw_t中的 methods里查找并将结果写进 cache

当再次使用该方法,直接在 cache里面查找使用

注:当前对象调用方法,就算是父类甚至是基类的方法,也会将该方法存储进自己(当前对象)的缓存中,而不会存储进父类的缓存中。另外注意缓存方法过程中散列表扩容时的重置问题

 1 #import <Foundation/Foundation.h>
2 #import "BSClassInfo.h" // 自定义实现文件,获取缓存相关
3
4 @interface Person : NSObject
5
6 - (void)personTest;
7 @end
8
9 @implementation Person
10 - (void)personTest{
11
12 NSLog(@"%s", __func__);
13 }
14 @end
15
16 //====================================
17 @interface Student : Person
18
19 - (void)studentTest;
20 @end
21
22 @implementation Student
23 - (void)studentTest{
24
25 NSLog(@"%s", __func__);
26 }
27 @end
28
29 //====================================
30 @interface GoodStudent : Student
31
32 - (void)goodStudentTest;
33 @end
34
35 @implementation GoodStudent
36
37 - (void)goodStudentTest{
38
39 NSLog(@"%s", __func__);
40 }
41 @end
42
43 //====================================
44 int main(int argc, const char * argv[]) {
45 @autoreleasepool {
46
47 GoodStudent *goodStudent = [[GoodStudent alloc] init];
48 mj_objc_class *goodStudentClass = (__bridge mj_objc_class *)[GoodStudent class];
49
50 // 多次调用方法,验证缓存状况
51 [goodStudent goodStudentTest];
52 [goodStudent studentTest];
53 [goodStudent personTest];
54 [goodStudent goodStudentTest];
55 [goodStudent studentTest];
56
57 NSLog(@"--------------------------");
58
59 // 遍历出 goodStudentClass中的缓存方法
60 cache_t cache = goodStudentClass->cache;
61 bucket_t *buckets = cache._buckets;
62 bucket_t bucket = buckets[(long long)@selector(studentTest) & cache._mask];
63 NSLog(@"%s %p", bucket._key, bucket._imp);
64 for (int i = 0; i <= cache._mask; i++) {
65 bucket_t bucket = buckets[i];
66 NSLog(@"%s %p", bucket._key, bucket._imp);
67 }
68 }
69 return 0;
70 }

日志输出:studentTest 和 personTest都是 GoodStudent中没有的方法,但是方法却全部缓存进当前调用类 GoodStudent中

链接 - BSClassInfo.h文件

https://pan.baidu.com/s/1q3PNtAjo6YCJDnCH70Gegw

c2ws

最新文章

  1. ui-router API
  2. php Windows系统 wamp集成环境下redis的使用
  3. CentOS 一个网卡设置多个IP
  4. 计算机网络——TCP三次、四次握手详解
  5. linux文件属性
  6. 【转】分享一份C语言写的简历
  7. lamp论坛搭建
  8. 1001 数组中和等于K的数对 1002 数塔取数问题 1003 阶乘后面0的数量 1004 n^n的末位数字 1009 数字1的数量
  9. JavaWeb开发环境搭建Eclipse配置Tomcat
  10. opencv 学习入门篇
  11. 从ruby实现时间服务器ntp同步功能也谈“逆向工程”
  12. Feature Preprocessing on Kaggle
  13. SDL中按键对应的值
  14. cxf方式实现webservice接口笔记
  15. OCP 相关课程列表
  16. 利用Python中的mock库对Python代码进行模拟测试
  17. vue问题大全
  18. GUI的最终选择Tkinter模块练习篇
  19. 《Effective C++》 目录:
  20. C语言 &#183; 排列数 &#183; 排列式

热门文章

  1. .Net NPOI 简单Demo,一看就会
  2. Mbps 与 MBps
  3. K8S 实用工具之一 - 如何合并多个 kubeconfig?
  4. LeetCode-2044 统计按位或能得到最大值子集的数目
  5. taro框架开发微信小程序遇到的问题
  6. 01.JavaSE学习
  7. 快速构建用户xlwings环境
  8. Hsm状态机init()和dispatch()流程
  9. 【剑指Offer】【栈】栈的压入、弹出序列
  10. foreach 和for