小码哥iOS学习笔记第十二天:Class结构
一、Class的结构
- 通过查看源码, 可以得出Class的底层结构如下图
- 一开始
class_data_bits_t bits;
指向ro
, 在加载的过程中创建了rw
, 此时的指向顺序是bits->rw->ro
二、class_rw_t
class_rw_t
里面的methods
、properties
、protocols
是二维数组,是可读可写的,包含了类的初始内容、分类的内容
三、class_ro_t
class_ro_t
里面的baseMethodList
、baseProtocols
、ivars
、baseProperties
是一维数组,是只读的,包含了类的初始内容
四、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_t
的mask
等于2
, 即_buckets的长度 - 1
- 当存储方法时, 会用
SEL & mask
, 获取到一个数字, 用这个数字做为索引, 将该方法存储到_buckets
中 - 假如有以下代码,
Person
继承自NSObject
, 有三个方法eat
、run
和say
Student
继承自Person
, 有三个方法learning
、exercises
和dancing
- 当一个
Student实例
调用learning
方法时, 就会用@selector(learning) & _buckets.mask
来获取存储的索引,然后将learning
方法存放到Student类对象
的cache
中 - 假设获取到的索引值为
0
, 那么散列表
的结构类似下图
- 如果
Student实例
调用父类Person
的eat
方法时, 根据@selector(eat) & _buckets.mask
的结果将becket_t
插入到相应位置 - 假设
@selector(eat) & _buckets.mask
结果是2
, 那么散列表
结构类似下图
即使
eat
是Person
中的方法, 但是Student
调用,也会存到Student
的cache
中当多个数都
&
一个固定值时, 那么肯定就有重复的可能出现, 比如
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
文件中有如下代码, 并在19
和21
行打断点
- 执行程序, 查看
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
中存储的方法, 其中包含了init
、goodStudentTest
和studentTest
- 在将代码修改为如下所示,
goodStudent
先调用5
个方法, 在打印缓存的方法列表
- 执行程序, 可以看到下面的打印
- 在上面已经知道, 在执行
[goodStudent personTest];
时已经进行了扩容, 所以此时的容量是8
, 由于清空了之前缓存的方法, 所以现在_buckets
中只存储了三个方法
通过索引获取方法
- 将
main
函数中的代码修改为如下所示
- 执行代码, 可以看到打印信息, 已经将
personTest
的方法和地址打印出来
- 我们可以通过控制台来验证打印的确实就是
personTest
方法
- 我们可以用同样的方式, 打印这三个方法
- 执行程序, 可以看到三个方法地址的打印
- 但是可以看到
personTest
和goodStudentTest
取出的地址是相同的 - 我们可以明确的知道这两个方法不同, 那么取出地址相同的原因就是
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结构相关推荐
- 小码哥iOS学习笔记第二天: OC对象的分类
Objective-C中的对象, 简称OC对象, 主要可以分为3种 instance对象(实例对象) class对象(类对象) meta-class对象(元类对象) 一.instance instan ...
- 小码哥iOS学习笔记第八天: block的底层结构
一.最简单的block 1.最简单的block结构 ^{NSLog(@"this is a block");NSLog(@"this is a block"); ...
- 小码哥iOS拓展班2期
小码哥iOS拓展班2期,视频全套不加密,有FM,直播,RAC,汇编项目,源码文档齐全. 注: 视频分为破解版和重录版,破解版码哥课堂和直播项目不全:重录版所有的视频和文档都是齐全的,只有直播项目第一天 ...
- Vue(小码哥王洪元)笔记07路由案例tabbar
1.tabbar案例01 进行了简单的布局 1.创建一个样式文件base.css body{margin:0; padding: 0;} 2.修改App.vue <template>< ...
- Vue(小码哥王洪元)笔记06路由,url的hash,history,router-linke,路由跳转,动态路由,懒加载,路由嵌套,router参数传递,导航守卫
1.什么是路由 路由器提供了两种机制:路由和传送 路由:数据报从来源到目的地的路径 传输:将输入端的数据转移到合适的输出端 路由有一个非常重要的概念教路由表 路由表本质上就是一个映射表,决定了数据包的 ...
- iOS-多线程-(小码哥底层原理笔记)
iOS中常见的多线程方案 GCD的常用函数 同步方式执行任务 dispatch_sync(dispatch_queue_t _Nonnull queue, ^(void)block) queue - ...
- 小码哥IOS大神班11期 大神班
链接:http://pan.baidu.com/s/1eRTq8oE 密码:mm8a 转载于:https://www.cnblogs.com/aleafo/p/10622806.html
- 最新李明杰小码哥ios开发视频教程四期
- IOS 学习笔记 2015-03-24 OC-API-常用结构体
一 标题 常用结构体二 API 1 NSRange 表示一个范围 A 实例化 NSRange rg={3,5};//第一参数是起始位置第二个参数是长度B 实例化 NSRange rg2=NSMakeR ...
最新文章
- 6.字符串解析(LeetCode第394题)
- C#和SqlServer中处理时间格式问题
- 牛客网(剑指offer) 第十二题 数值的整数次方
- Python 爬虫知识点 - 淘宝商品检索结果抓包分析(续一)
- IE8采用IE7模式
- linux 粘贴网站地址,linux 复制粘贴
- DeleteCommand属性---删除数据集指定的行保存到数据源中
- ipad能安装python么_ipad上能安装python吗
- mysql面试常问 1: 谈谈MySQL表级锁和行级锁
- mysql 5.5.50_mysql 5.5.50 乱码解决
- 挤爆了!故宫首次晚间开放:预约票平台一度502
- Could not connect to SMTP host: smtp.163.com, port: 25;阿里云 ECS
- Nexus6P 设置Debug模式
- 【Hadoop】HDFS三组件:NameNode、SecondaryNameNode和DataNode
- DEBUG模式下,视频丢包严重;RELEASE就好了
- FastDFS实现原理及流程
- 金蝶K3CLOUD7.2内部培训PPT下载
- 用Python3创建httpServer
- [转]一个中高级PHP工程师所应该具备的能力
- HMACSHA1 加密算法