iOS笔记 - Runtime 01:前期准备(isa结构 | Class结构 | 方法缓存)
前言
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)。散列表扩容的同时会把原表中的内容全部清空,原来的方法缓存不复存在,是一份崭新的散列表
③ 方法缓存原理
如果缓存中没有该方法,就在 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
iOS笔记 - Runtime 01:前期准备(isa结构 | Class结构 | 方法缓存)的更多相关文章
- 关于iOS的runtime
runtime是一个很有意思的东西,如果你学iOS开发很经常就会用到或被问到runtime.那么runtime是什么呢,如何去了解它. runtime:中文名 运行时,系统在编译时留下的一些 类型,操 ...
- iOS 认识runtime 中的三个指针 isa , IMP , SEL
runtime中函数调用经常被提及的三个概念 isa,IMP,SEL 一 isa:是类指针,之所以说isa是指针是因为Class其实是一个指向objc_class结构体的指针,而isa 是它唯一的私 ...
- iOS回顾笔记( 01 )
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,bi ...
- iOS回顾笔记( 01 )-- XIB和纯代码创建应用的对比
header{font-size:1em;padding-top:1.5em;padding-bottom:1.5em} .markdown-body{overflow:hidden} .markdo ...
- iOS:runtime最全的知识总结
runtime 完整总结 好东西,应该拿出来与大家分享... 南峰子博客地址:http://southpeak.github.io/blog/categories/ios/ 原文链接:http://w ...
- iOS开发-Runtime详解(简书)
简介 Runtime 又叫运行时,是一套底层的 C 语言 API,其为 iOS 内部的核心之一,我们平时编写的 OC 代码,底层都是基于它来实现的.比如: [receiver message]; // ...
- iOS开发-Runtime详解
iOS开发-Runtime详解 简介 Runtime 又叫运行时,是一套底层的 C 语言 API,其为 iOS 内部的核心之一,我们平时编写的 OC 代码,底层都是基于它来实现的.比如: [recei ...
- 【Unity Shaders】学习笔记——SurfaceShader(二)两个结构体和CG类型
[Unity Shaders]学习笔记——SurfaceShader(二)两个结构体和CG类型 转载请注明出处:http://www.cnblogs.com/-867259206/p/5596698. ...
- 荼菜的iOS笔记--UIView的几个Block动画
前言:我的第一篇文章荼菜的iOS笔记–Core Animation 核心动画算是比较详细讲了核心动画的用法,但是如你上篇看到的,有时我们只是想实现一些很小的动画,这时再用coreAnimation就会 ...
- Fortran学习笔记:01 基本格式与变量声明
Fortran学习笔记目录 01 基本格式与变量声明 格式 固定格式(Fixed Format):Fortran77 程序需要满足一种特定的格式要求,具体形式参考教材 自由格式(Free Format ...
随机推荐
- 电子辅助的个体化严密控制策略比常规方法更有效地帮助早期RA实现全面控制病情[EULAR2015_THU0122]
电子辅助的个体化严密控制策略比常规方法更有效地帮助早期RA实现全面控制病情 THU0122 A TAILORED TELEMONITORING TIGHT CONTROL STRATEGY FOR P ...
- Postgresql 全局索引与分区索引对于SQL性能影响的比较及DDL操作后分区全局索引是否会失效
Postgresql 提供了对于分区表 global index 的支持.global index 不仅提供了对于唯一索引功能的改进(无需包含分区键),而且在性能上相比非global index (l ...
- linux系统安装MySQL服务,详细图文教程
注:linux系统在安装操作系统时,如果选择了开发工具和兼容库后,一般默认都会安装MySQL服务的部分程序包.如果我们要自定义的安装全新的MySQL服务,就必须先把这些已经安装的程序包都卸载掉.否则, ...
- mysql的双1设置是什么?
innodb_flush_log_at_trx_commit和sync_binlog 两个参数是控制MySQL磁盘写入策略以及数据安全性的关键参数.如果innodb_flush_log_at_trx_ ...
- pat乙级 1017 A除以B 模拟除法
#include <stdio.h> #define MAX_A 1000 int A[MAX_A]; int B; /* 除数 */ int num; /* A 被除数有多少位 */ v ...
- 多资产VAR风险--基于python处理
一.数据准备,先在excel表格上计算每日的波动率: excel数据为: 二.数据导入: import pandas as pd import numpy as np import akshare a ...
- SQLServer错误:expected a simple type, a tuple or a list
dict_data = { 'request_id': request_id, 'original_id': request_body.original_id, 'unit_type': reques ...
- 运行springboot的时候访问本地图片地址的问题
@Configuration public class MyConfigrauration implements WebMvcConfigurer { /** * 当有请求经过[/myUpload/* ...
- 使用open打开子页面时,父页面关闭子页面操作
<button onclick="open1()">kaiqi</button> <button onclick="close1()&quo ...
- 解决移动H5页面的刷组造成件传值数据丢失问题
问题描述 当前页面由2个组件组成分别命名成a,b,其中组件b(子)需要组件a(父)内接口响应的时间字段,因为组件b不能实时的接收到数据 因此组件b在接收数据的时用定时器加了一个延迟,但是在网络不好或者 ...