一、概念、常见应用、使用方式
1.1概念:(关键字:C的API 源C/C++/汇编 动建类/对象 消息传递/转发 类结构体 方法C函数 调的方法objc_msgSend 封装)
Runtime顾名思义,运行时,是一套C语言的API,底层源码是C/C++/汇编。
OC是一门动态性较强的编程语言,即允许很多操作推迟到程序运行时再运行,故需要运行时系统动态创建类和对象、进行消息传递和转发。
OC的动态性就是由Runtime来支撑和实现的,OC代码底层都是转换成RuntimeAPI调用,比如类转成runtime库里的结构体等数据类型,方法转成C语言函数,调的方法转成了objc_msgSend函数。Runtime库里包含跟类、成员变量、方法相关的API,封装了很多和动态性相关的函数。
1.2常见应用:(关键字:关加属 遍all员 动建员 动加法 换法 法异消转)
用关联对象给分类添加属性、遍历所有类的成员变量(比如文本框占位文字的颜色、字典转模型、自动解归档)、为类动态添加成员变量、为类动态添加新的方法、交换方法实现、方法找不到的异常用消息转发机制解决。
1.3使用方式:(关键字:运时为分类绑属性 影响全app)
禁多点:.m动绑属监点击 略重复点 .m类型assign控制是否能点 限时只响应1次
导入<objc/message.h>,<objc/runtime.h>
category只能新增方法,不能为直接原有类添加属性;但通过runtime.h运行时来为分类绑定属性,正常绑定get/set方法。
注意:新增的属性会对整个程序产生影响,而不是导入头文件才有效。
利用runtime的selector禁止点击事件多次触发 具体为UIControl或UIButton创建分类。
在.m文件#import<objc/runtime.h>使用runtime动态绑定属性监听点击事件,忽略重复点击,设置.h中定义的assign类型控制是否能点击属性eventTimeInterval,使其在规定时间内只响应一次点击事件。
分类的load(所有NS子类都有的运时载的类方法 执早 不需手调 唯一 无抢资源问题)方法中加method swizzling和替法 )
先添加一个category,然后在category中的+(void)load中添加method swizzling方法 ,用来替换的方法也写在category中,
+(void)load方法:所有NSObject子类都具备的用于运行时加载处理的一个类方法,执行比较早且不需要手动调用,且该方法具有唯一性,不用担心资源抢夺的问题。C语言的API
二、数据结构
objc_object objc_class cache_t class_rw_t class_ro_t method_t TypeEncoding
2.1id=objc_object isa_t isa相关操作 (获取isa指向的类对象 通过isa指针获取元类对象) 弱引用(相关方法如标记对象 指针) 关联对象 内存管理 相关的方法实现retain release @atuoreleasepool
2.2Class=objc_class结构体 类对象 (继承自objc_object)Class包含Class superClass; cache_t cache;方法缓存 class_data_bits_t bits;类定义的属性、变量、方法都在里面
2.2.1方法缓存cache_t (关键字:快查 增扩的哈希表 缓存曾调法 局原理)
快速查找方法执行函数、用可增量扩展的哈希表/散列表结构 缓存曾调用过的方法 局部性原理的最佳应用
cache_t存储了散列表bucket_t的数组,散列表长度,已缓存方法数量;bucket_t含key和IMP(IMP无类型的函数指针)
2.2.2
class_data_bits_t是对class_rw_t的封装,class_rw_t是对class_ro_t的封装
class_rw_t 里面的方法、协议、属性是二维数组,包含类和分类的初始内容
class_ro_t里面的base方法methodList(里面是多个method_t)、协议、属性、const ivars是一维数组,是只读的,包含类的初始内容
method_t是对方法和函数的封装 struct method_t{
SEL name;//函数名
const char *types;//编码(返回值类型、参数类型)
IMP imp;//指向函数的指针(函数地址)}
SEL方法\函数名,一般叫选择器,底层结构与char*类似;
不同类同名方法,所对应的方法选择器是相同的typedef struct object_selector *SEL;
SEL可通过@selector()和sel_registerName()获得;可通过sel_getName()和NSStringFromSelector()转成字符串
IMP代表函数的具体实现 typedef id _Nullable(*IMP)(id _Nonnull,SEL _Nonnull,……);
Type Encoding
iOS中提供@encode的指令,可将具体的类型表示成字符串编码
2.3 isa指针
指针型isa和非指针型isa 值部分代表class的地址 实例对象--isa指向-->类对象class--isa指向-->元类对象metaClass
在arm64架构前,isa是存类和元类对象的内存地址的普通指针;之后优化后的isa变成共用体union结构,用位域存储更多信息
union isa_t
{ Class cls;
uintptr_t bits;
struct{
uintptr_t nonopointer 0普通指针,存类和元类对象的内存地址 1
uintptr_t has_assoc是否设置过关联对象
uintptr_t has_cxx_dtor是否有C++析构函数 weakly_referenced是否被弱引用指向过 若没有释放得更快
uintptr_t deallocating对象是否正在释放
uintptr_t has_sidetable_rc引用计数器是否过大无法存在isa中 若为1引用计数存储在SideTable类的属性中
uintptr_t extra_rc里面存储的值是引用计数减1
uintptr_t shiftcls 存储着类、元类对象的内存地址的信息 magic 用于在调试时分辨对象是否未完成初始化
uintptr_t method_t对方法/函数的封装
三、类与元类对象、消息传递机制
3.1类与元类对象存储、指向
类对象存 实例 方法列表等信息。根类指向nil。继承自objc_Object,所以有isa指针,通过isa指针找到元类对象,从而访问关于类方法列表相关信息
元类对象存 类 方法列表等信息。根元类对象的superclass指针指向根类对象。
3.1.2如果调用的类方法,没有对应实现,但有同名实例方法实现,会不会发生崩溃,会不会产生实际调用?
调用类方法,从元类方法列表查找,沿着红色箭头逐级父类查找,当在元类方法列表中找不到,就会到根类对象找 同名的实例方法实现
3.2消息传递过程
比如调用实例方法A,系统根据当前实例的isa指针找到类对象,在类对象中遍历方法列表,查找同名的方法实现,如果没有查找到会顺次以superclass的指向查找父类类对象方法列表,查找根类类对象方法列表,一直向上查找,如果没有查找到,就会走到消息转发流程。
比如调用类方法B,通过类对象的isa指针找到元类对象,顺次遍历方法列表,直到根元类对象,再到根类对象,再到nil
父类,子类,给定实例id类型 isa成员变量,指向实例对应的类对象,classA创建出instanceA,isa指针指向classA,子类的父类也有一个实例
3.2.1消息传递过程中的方法查找
当前类、父类中查找
当前类中查找:对于已排序好的列表,二分查找方法对应执行函数;没排序的,一般遍历。
四、动态方法解析/ 添加、消息转发、Method-Swizzling
objc_magSend 消息传递 receiver isa receiverClass superclass superClass cache class_rw_t 已二分查找 无遍历查找
动态方法解析 +resolveInstanceMethod: +resolveClassMethod
消息转发机制
五、super的本质
super调用,底层会转换为objc_msgSendSuper2函数的调用,接收2个参数 struct objc_super2 和SEL
struct objc_super2里有2个参数 struct objc_super2{ id receiver;//消息接收者
Class current_class;//receiver的Class对象 };
六、LLVM的中间代码IR
OC在变成机器代码前,会被LLVM编译器转换成中间代码Intermidiate Representation。可用命令行clang -emit -llvm -S main.m
语法简介:@ % alloca i32 align store load icmp br label call
@全局变量 %局部变量
alloca 在当前执行的函数堆栈帧中分配内存,当该函数返回到其调用者时,将自动释放内存
i32 32位字节的整数
align对齐 store写入 load读出 icmp两个整数值比较,返回布尔值 br选择分支 根据条件转向label不根据条件跳转类似goto label代码标签 call调用函数