Runtime简介
Objective-C是一门动态性比较强的编程语言,跟C、C++等语言有着很大的不同,允许很多操作推迟到程序运行时再进行。
Objective-C的动态性是由Runtime API来支撑和实现的,Runtime API提供的接口基本都是C语言的,源码由C\C++\汇编语言编写。
学习 Runtime 机制前需要先了解OC 对象本质,否则看不懂以下内容。
平时编写的OC代码,底层都是转换成了Runtime API进行调用。
class的结构
//struct objc_class 结构
struct objc_class{
Class isa;
Class superclass;
Cache_t cache;//方法缓存
class_data_bit_t bit;//用于获取类的具体信息
}
// bit & FAST_DATA_MASK 得到类的具体信息
struct class_rw_t { // rw表示可读写
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods; //method_t 二维数组
property_array_t properties; //属性二维数组
protocol_array_t protocols; //协议二维数组
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
}
//struct class_ro_t 的结构
struct class_ro_t { //ro表示只读
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const 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;
}
class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容
class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容
//method_t结构
struct method_t{
SEL name;//函数名
const char *type;//编码(包含返回值类型,参数类型)
IMP imp;//函数指针(指向函数的指针)
}
-
SEL代表方法\函数名,一般叫做选择器,底层结构跟char *(字符串)类似
不同类中相同名字的方法,所对应的方法选择器是相同的 -
type是包含了函数返回值、参数的编码字符串
方法缓存
Class内部结构中有个方法缓存(cache_t),用散列表(哈希表,空间换取时间增加查找速度)来缓存曾经调用过的方法,可以提高方法的查找速度
//cathe_t结构
struct cahe_t{
struct bucket_t *buckets;//散列表
mask_t _mask;//散列表的长度 - 1
mask_t _occupied;//已经缓存的方法数量
}
//buket_t结构
struct_t buket_t{
cahe_ket_t _key;//SEL作为key
IMP _imp;//函数的内存地址
}
objc_msgSend执行流程
OC中的方法调用,其实都是转换为objc_msgSend(receiver,message)函数的调用。
objc_msgSend的执行流程可以分为3大阶段:消息发送;动态方法解析;消息转发。
-
objc_msgSend第一阶段:消息发送
消息发送流程图 -
objc_msgSend第二阶段:动态方法解析
动态方法解析流程图
开发者可以实现以下方法,来动态添加方法实现
+ (BOOL)resolveClassMethod:(SEL)sel
+ (BOOL)resolveInstanceMethod:(SEL)sel
动态解析对象方法
void c_other(id self, SEL _cmd)
{
NSLog(@"c_other:%@ - %@", self, NSStringFromSelector(_cmd));
}
- (void)other
{
NSLog(@"%s", __func__);
}
+ (BOOL)resolveClassMethod:(SEL)sel
{
if (sel == @selector(test)) {
// 参数 类对象,sel, 方法实现地址,方法参数编码(包含返回值,参数)
// 方式1:直接添加方法实现
// class_addMethod(object_getClass(self), sel, (IMP)c_other, "v16@0:8");
// 方式2: 获取其他方法添加
// 获取其他方法
Method method = class_getInstanceMethod(self, @selector(other));
// 动态添加test方法的实现
class_addMethod(self, sel,
method_getImplementation(method),
method_getTypeEncoding(method));
return YES;
}
return [super resolveClassMethod:sel];
}
动态解析类方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(test)) {
// 获取其他方法
struct method_t *method = (struct method_t *)class_getInstanceMethod(self, @selector(other));
// 动态添加test方法的实现
class_addMethod(self, sel, method->imp, method->types);
// 返回YES代表有动态添加方法
return YES;
}
return [super resolveInstanceMethod:sel];
}
动态解析过后,会重新走“消息发送”的流程
“从receiverClass的cache中查找方法”这一步开始执行
-
objc_msgSend第三阶段:消息转发
image.png
开发者可以实现以下方法实现消息转发
- (id)forwardingTargetForSelector:(SEL)aSelector
//或
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- (void)forwardInvocation:(NSInvocation *)anInvocation
消息转发
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) {
// Cat 对象实现了 test 方法
// objc_msgSend([[MJCat alloc] init], aSelector)
return [[Cat alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
// 方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) {
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
}
return [super methodSignatureForSelector:aSelector];
}
// NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
// anInvocation.target 方法调用者
// anInvocation.selector 方法名
// [anInvocation getArgument:NULL atIndex:0]
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
// anInvocation.target = [[Cat alloc] init];
// [anInvocation invoke];
// 上面两句等同于下面这句
[anInvocation invokeWithTarget:[[Cat alloc] init]];
}
Category的加载处理过程
我们日常写的分类的底层结构如下
struct category_t {
const char *name;//类名
classref_t cls;
struct method_list_t *instanceMethods;//对象方法列表
struct method_list_t *classMethods;//类方法列表
struct protocol_list_t *protocols; //协议列表
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
- category的加载处理过程
1.通过runtime加载某个类的category数据
2.把所有的category的方法、属性、协议数据合并到一个大数组中
后面参与编译的category数据,会在数组的前面
3.将合并后的分类数据(方法、属性、协议),插到类原来数据的前面
Runtime应用
- 查看对象的私有成员变量,然后用 KVC 赋值改变私有成员变量的值
// 打印查看某个类的所有成员变量
- (void)lookIvarsFromClass:(Class)cls{
unsigned int count;
Ivar *ivars = class_copyIvarList(cls, &count);
for (int i = 0; i < count; i ++) {
Ivar ivar = ivars[i];
NSLog(@"%s",ivar_getName(ivar));
}
free(ivars);
}
//改变placeholderLabel字体颜色
- (void)changePlaceholderColor{
[self.textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[super touchesBegan:touches withEvent:event];
// 查看UITextField的所有成员变量
[self lookIvarsFromClass:[UITextField class]];
}
- 动态添加属性
为 Person 对象动态添加属性(间接实现属性效果)
// Person+kj.m 分类 文件
@implementation Person (KJ)
static char nameKey;
- (void)setName:(NSString *)name{
objc_setAssociatedObject(self, &nameKey, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)name{
return objc_getAssociatedObject(self, &nameKey);
}
@end
- 字典转模型(MJExtension 框架)利用 Runtime遍历所有的属性或成员变量,然后利用 KVC设值
+ (instancetype)objectWithJson:(NSDictionary *)json{
id obj = [[self alloc] init];
unsigned int count;
Ivar *ivars = class_copyIvarList(self, &count);
// 遍历 obj 成员变量
for (int i = 0; i < count; i ++) {
Ivar ivar = ivars[i];
NSMutableString *ivarName = [NSMutableString stringWithUTF8String:ivar_getName(ivar)];
// 删掉前面下划线
[ivarName deleteCharactersInRange:NSMakeRange(0,1)];
// KVC 赋值
[self setValue:json[ivarName] forKeyPath:ivarName];
}
free(ivars);
return obj;
}
- 利用消息转发解决找不到方法App闪退问题
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
// 本来能调用的方法
if ([self respondsToSelector:aSelector]) {
return [super methodSignatureForSelector:aSelector];
}
// 找不到方法
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
// 找不到的方法,都会来到这里
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"找不到%@方法", NSStringFromSelector(anInvocation.selector));
}
- 替换方法实现(Method Swizzling) hook 系统方法,自定义处理自己的逻辑
#import "UIViewController+KJ.h"
#import <objc/runtime.h>
@implementation UIViewController (KJ)
// 类加载时调用,只会调用一次
+ (void)load{
Method hookMethod = class_getInstanceMethod(self, NSSelectorFromString(@"hookViewDidLoad"));
Method originalMethod = class_getInstanceMethod(self, @selector(viewDidLoad));
method_exchangeImplementations(hookMethod, originalMethod);
}
- (void)hookViewDidLoad{
// 调用原来的方法,咋看是循环调用,其实方法实现被交换了,这样只会调用原来的
[self hookViewDidLoad];
NSLog(@"加载了%@",[self class]);
// 自定义业务,判断 class 统一埋点点处理等等
}
@end
- ......
网友评论