学习MJ的视频课程,整理总结知识点–Runtime API

本文主要简单介绍Runtime常用的一些API,相关的API包括类、成员变量、属性、方法等。另,文章前面已介绍不少Runtime的API,这里不再重复

中间代码

先补充一些知识点,关于查看OC代码转换后的底层实现的手段

  1. 通过clang转换为cpp文件
    用于查看OC代码转化的C、C++代码,导出的一般是编译阶段的代码,和运行时真实调用的底层代码可能会有细小的差异,可以用于参考,但是代码逻辑基本上一致,可以用于分析OC的底层实现。
1
2
3
4
5
objc_msgSendSuper({self, class_getSuperclass(objc_getClass("MJPerson"))}, @selector(forwardInvocation:), anInvocation);
int a = 10;
int b = 20;
int c = a + b;
test(c);
  1. Xcode断点Debug选择Always Show Disassembly查看运行阶段的汇编代码
1
2
3
4
5
6
7
8
0x100000e8e <+62>:  movq   0x29b(%rip), %rdx         ; "forwardInvocation:"
0x100000e95 <+69>: leaq -0x28(%rbp), %rdi
0x100000e99 <+73>: movq %rsi, -0x40(%rbp)
0x100000e9d <+77>: movq %rdx, %rsi
0x100000ea0 <+80>: movq -0x40(%rbp), %rdx
0x100000ea4 <+84>: callq 0x100000ef0 ; symbol stub for: objc_msgSendSuper2
0x100000ea9 <+89>: movl $0xa, -0x2c(%rbp)
0x100000eb0 <+96>: movl $0x14, -0x30(%rbp)
  1. Xcode选择Product -> Assembly xx文件,转成汇编
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    .loc	2 29 5                  ## Interview01-super/MJPerson.m:29:5
    movq %rdx, -40(%rbp)
    movq L_OBJC_CLASSLIST_SUP_REFS_$_(%rip), %rdx
    movq %rdx, -32(%rbp)
    movq L_OBJC_SELECTOR_REFERENCES_.2(%rip), %rdx
    leaq -40(%rbp), %rdi
    movq %rsi, -64(%rbp) ## 8-byte Spill
    movq %rdx, %rsi
    movq -64(%rbp), %rdx ## 8-byte Reload
    callq _objc_msgSendSuper2
    4、clang导出中间代码
    苹果是通过LLVM把OC代码转成最终执行的机器码,大致过程OC -> 中间代码 -> 机器码
    LLVM跨平台的,汇编、机器码是区分平台的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
define internal void @"\01-[MJPerson forwardInvocation:]"(%0*, i8*, %1*) #1 {
%4 = alloca %0*, align 8
%5 = alloca i8*, align 8
%6 = alloca %1*, align 8
%7 = alloca %struct._objc_super, align 8
%8 = alloca i32, align 4
%9 = alloca i32, align 4
%10 = alloca i32, align 4
store %0* %0, %0** %4, align 8
store i8* %1, i8** %5, align 8
store %1* %2, %1** %6, align 8
%11 = load %0*, %0** %4, align 8
%12 = load %1*, %1** %6, align 8
%13 = bitcast %0* %11 to i8*
%14 = getelementptr inbounds %struct._objc_super, %struct._objc_super* %7, i32 0, i32 0
store i8* %13, i8** %14, align 8
%15 = load %struct._class_t*, %struct._class_t** @"OBJC_CLASSLIST_SUP_REFS_$_", align 8
%16 = bitcast %struct._class_t* %15 to i8*
%17 = getelementptr inbounds %struct._objc_super, %struct._objc_super* %7, i32 0, i32 1
store i8* %16, i8** %17, align 8
%18 = load i8*, i8** @OBJC_SELECTOR_REFERENCES_, align 8, !invariant.load !8
call void bitcast (i8* (%struct._objc_super*, i8*, ...)* @objc_msgSendSuper2 to void (%struct._objc_super*, i8*, %1*)*)(%struct._objc_super* %7, i8* %18, %1* %12)
store i32 10, i32* %8, align 4
store i32 20, i32* %9, align 4
%19 = load i32, i32* %8, align 4
%20 = load i32, i32* %9, align 4
%21 = add nsw i32 %19, %20
store i32 %21, i32* %10, align 4
%22 = load i32, i32* %10, align 4
call void @test(i32 %22)
ret void
}

Objective-C在变为机器代码之前,会被LLVM编译器转换为中间代码(Intermediate Representation)

可以使用以下命令行指令生成中间代码c
lang -emit-llvm -S main.m

语法简介
@ - 全局变量
% - 局部变量
alloca - 在当前执行的函数的堆栈帧中分配内存,当该函数返回到其调用者时,将自动释放内存
i32 - 32位4字节的整数
align - 对齐
load - 读出,
store 写入
icmp - 两个整数值比较,返回布尔值
br - 选择分支,根据条件来转向
label,不根据条件跳转的话类似
gotolabel - 代码标签
call - 调用函数

具体可以参考官方文档:https://llvm.org/docs/LangRef.html

Runtime API - 类

动态创建一个类,添加属性,添加方法,注册类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
MJPerson *person = [[MJPerson alloc] init];
[person run];

object_setClass(person, [MJCar class]);
[person run];

NSLog(@"%d %d %d",
object_isClass(person),
object_isClass([MJPerson class]),
object_isClass(object_getClass([MJPerson class]))
);

// 创建类
Class newClass = objc_allocateClassPair([NSObject class], "MJDog", 0);
class_addIvar(newClass, "_age", 4, 1, @encode(int));
class_addIvar(newClass, "_weight", 4, 1, @encode(int));
class_addMethod(newClass, @selector(run), (IMP)run, "v@:");
// 注册类
objc_registerClassPair(newClass);

// 在不需要这个类时释放
objc_disposeClassPair(newClass);

Runtime API - 成员变量

Runtime API - 类

Runtime API - 成员变量

Runtime API - 属性

Runtime API - 方法

Runtime API - 方法

应用-找textfiled

Ivar *ivars
C语言取数据时数组和指针没什么区别
Ivar ivar = *(ivars + i)

Runtime实现字典转模型

ivarlist

Runtime API – 方法

拦截所有按钮的点击事件
方法交换,交换的是class_rw_t->methods->method_lsit(method_t)->imp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@implementation UIControl (Extension)

+ (void)load {
// hook:钩子函数
Method method1 = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
Method method2 = class_getInstanceMethod(self, @selector(mj_sendAction:to:forEvent:));
method_exchangeImplementations(method1, method2);
}

- (void)mj_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
NSLog(@"%@-%@-%@", self, target, NSStringFromSelector(action));
// 调用系统原来的实现
[self mj_sendAction:action to:target forEvent:event];
}

@end

NSMutableArray数据插空处理

类簇:NSString、NSArray、NSDictionary,真实类型是其他类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@implementation NSMutableArray (Extension)

+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 类簇:NSString、NSArray、NSDictionary,真实类型是其他类型
Class cls = NSClassFromString(@"__NSArrayM");
Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
Method method2 = class_getInstanceMethod(cls, @selector(mj_insertObject:atIndex:));
method_exchangeImplementations(method1, method2);
});
}

- (void)mj_insertObject:(id)anObject atIndex:(NSUInteger)index {
if (anObject == nil) return;
[self mj_insertObject:anObject atIndex:index];
}

@end

字典

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@implementation NSMutableDictionary (Extension)

+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class cls = NSClassFromString(@"__NSDictionaryM");
Method method1 = class_getInstanceMethod(cls, @selector(setObject:forKeyedSubscript:));
Method method2 = class_getInstanceMethod(cls, @selector(mj_setObject:forKeyedSubscript:));
method_exchangeImplementations(method1, method2);

Class cls2 = NSClassFromString(@"__NSDictionaryI");
Method method3 = class_getInstanceMethod(cls2, @selector(objectForKeyedSubscript:));
Method method4 = class_getInstanceMethod(cls2, @selector(mj_objectForKeyedSubscript:));
method_exchangeImplementations(method3, method4);
});
}

- (void)mj_setObject:(id)obj forKeyedSubscript:(id<NSCopying>)key {
if (!key) return;

[self mj_setObject:obj forKeyedSubscript:key];
}

- (id)mj_objectForKeyedSubscript:(id)key {
if (!key) return nil;

return [self mj_objectForKeyedSubscript:key];
}

@end

fishhook

总结

什么是Runtime?平时项目中有用过么?
OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数平时编写的OC代码,底层都是转换成了Runtime API进行调用

应用
关联对象给分类添加属性
获取遍历类的成员变量,修改私有变量
字典转模型、自动归档解档
交换方法实现(交换系统的方法,导航栏样式)
利用消息转发机制解决方法找不到的异常问题

参考和源码

源码:

评论