objc_msgSend 流程分析

文章目录
  1. 1. 系统在 runtime 时如何查找方法
    1. 1.1. objc_class 源码结构
    2. 1.2. method_t
    3. 1.3. Type Encoding
    4. 1.4. 方法缓存
  2. 2. objc_msgSend执行流程
    1. 2.1. 消息发送
    2. 2.2. 动态方法解析
    3. 2.3. 消息转发
      1. 2.3.1. forwarding
      2. 2.3.2. 方法签名
  3. 3. Runtime API
    1. 3.1. 类相关
    2. 3.2. 成员变量相关
    3. 3.3. 属性相关
    4. 3.4. 方法相关
  4. 4. 具体应用

OC对象分析

系统在 runtime 时如何查找方法

oc 实例对象不存储方法的,方法都在类对象里面

整体流程图

objc_class 源码结构

objc_class

  • class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容
  • class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容

method_t

对方法\函数的封装

1
2
3
4
5
struct method_t {
SEL name; // char* 函数名
const char *types; // 参数,返回值编码
MethodListIMP imp; // 函数实现地址
};
ivar_t, property_t 源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct ivar_t {
int32_t *offset;
const char *name;
const char *type;
// alignment is sometimes -1; use alignment() instead
uint32_t alignment_raw;
uint32_t size;

uint32_t alignment() const {
if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
return 1 << alignment_raw;
}
};

struct property_t {
const char *name;
const char *attributes;
};

Type Encoding


type_encode

方法缓存

objc_class 中有个方法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度

1
2
3
4
5
6
7
8
9
struct cache_t {
struct bucket_t *_buckets; // 散列表
mask_t _mask; // 散列表长度 - 1
mask_t _occupied; // 已经缓存方法的数量
};
struct bucket_t {
SEL _sel; // 作为 key
uintptr_t _imp; // value
};

objc_msgSend执行流程

oc 是 c 的扩展,在 oc 方法的触发有两种 1. 发送消息,2. 函数调用
发送消息都是转换为objc_msgSend函数的调用
objc_msgSend的执行流程可以分为3个阶段

  1. 消息发送
  2. 动态方法解析
  3. 消息转发

消息发送

消息查找过程

动态方法解析

  1. 消息查找失败,跑到 resolveClassMethod or resolveInstanceMethod
  2. 这个时候得到要添加方法的 name,参数列表
  3. 动态添加方法,class_addMethod,添加的方法会加到 object_class 对象的 class_rw_t 的 methods 中
  4. 然后 retry 消息发送流程
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
// 可以添加 c 函数
void c_func(id self, SEL _cmd) {
NSLog(@"c_func: %@-%@", self, NSStringFromSelector(_cmd));
}

+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(test)) {
// 第一个参数是object_getClass(self)
class_addMethod(object_getClass(self), sel, (IMP)c_other, "v16@0:8");
return YES;
}
return [super resolveClassMethod:sel];
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(unfound)) {
// 动态添加 unfound 方法的实现
Method method = class_getInstanceMethod(self, @selector(customFunc));

// 动态添加test方法的实现
class_addMethod(self, sel,
method_getImplementation(method),
method_getTypeEncoding(method));
return YES;
}
return [super resolveInstanceMethod:sel];
}

- (void)customFunc{
NSLog(@"%s", __func__);
}

消息转发

如果重载动态方法解析 resolveClassMethod or resolveInstanceMethod 但方法内部没有添加方法那么就会走消息转发

forwarding

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
33
34
35
int __forwarding__(void *frameStackPointer, int isStret) {
id receiver = *(id *)frameStackPointer;
SEL sel = *(SEL *)(frameStackPointer + 8);
const char *selName = sel_getName(sel);
Class receiverClass = object_getClass(receiver);

// 调用 forwardingTargetForSelector:
if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
id forwardingTarget = [receiver forwardingTargetForSelector:sel];
if (forwardingTarget && forwardingTarget != receiver) {
return objc_msgSend(forwardingTarget, sel, ...);
}
}

// 调用 methodSignatureForSelector 获取方法签名后再调用 forwardInvocation
if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
if (methodSignature && class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];

[receiver forwardInvocation:invocation];

void *returnValue = NULL;
[invocation getReturnValue:&value];
return returnValue;
}
}

if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
[receiver doesNotRecognizeSelector:sel];
}

// The point of no return.
kill(getpid(), 9);
}

方法签名

如果 forwarding 方法没有是实现,or 返回的是 nil,那么会 objc 会调用 方法签名
此时你要实现下面两个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(test:)) {
return [NSMethodSignature signatureWithObjCTypes:"i@:i"];
}
return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
// 参数顺序:receiver、selector、other arguments
// 如果不设置那么就是最开始 发送消息的 实例对象&方法
anInvocation.target == [[NewObjec alloc] init]
anInvocation.selector == otherFunc:
int ret;
[anInvocation getReturnValue:&ret];
NSLog(@"%d", ret);
}
NSMethodSignature, NSInvocation(swift 不可用)
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
// 方法签名封装方法参数,返回值,对象
NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available")
@interface NSMethodSignature : NSObject
+ (nullable NSMethodSignature *)signatureWithObjCTypes:(const char *)types;
@property (readonly) NSUInteger numberOfArguments;
- (const char *)getArgumentTypeAtIndex:(NSUInteger)idx NS_RETURNS_INNER_POINTER;
@property (readonly) NSUInteger frameLength;
- (BOOL)isOneway;
@property (readonly) const char *methodReturnType NS_RETURNS_INNER_POINTER;
@property (readonly) NSUInteger methodReturnLength;
@end

// 根据方法签名获得
NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available")
@interface NSInvocation : NSObject
+ (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig;
@property (readonly, retain) NSMethodSignature *methodSignature;
- (void)retainArguments;
@property (readonly) BOOL argumentsRetained;
@property (nullable, assign) id target;
@property SEL selector;
- (void)getReturnValue:(void *)retLoc;
- (void)setReturnValue:(void *)retLoc;
- (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
- (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
- (void)invoke;
- (void)invokeWithTarget:(id)target;
@end

Runtime API

类相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//动态创建一个类(参数:父类,类名,额外的内存空间)
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
//注册一个类(要在类注册之前添加成员变量)
void objc_registerClassPair(Class cls)
//销毁一个类
void objc_disposeClassPair(Class cls)
//获取isa指向的Class
Class object_getClass(id obj)
//设置isa指向的Class
Class object_setClass(id obj, Class cls)
//判断一个OC对象是否为Class
BOOL object_isClass(id obj)
//判断一个Class是否为元类
BOOL class_isMetaClass(Class cls)
//获取父类
Class class_getSuperclass(Class cls)

成员变量相关

1
2
3
4
5
6
7
8
9
10
11
12
//获取一个实例变量信息
Ivar class_getInstanceVariable(Class cls, const char *name)
//拷贝实例变量列表(最后需要调用free释放)
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
//设置和获取成员变量的值
void object_setIvar(id obj, Ivar ivar, id value)
id object_getIvar(id obj, Ivar ivar)
//动态添加成员变量(已经注册的类是不能动态添加成员变量的)
BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)
//获取成员变量的相关信息
const char *ivar_getName(Ivar v)
const char *ivar_getTypeEncoding(Ivar v)

属性相关

1
2
3
4
5
6
7
8
9
10
11
12
//获取一个属性
objc_property_t class_getProperty(Class cls, const char *name)
//拷贝属性列表(最后需要调用free释放)
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
//动态添加属性
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)

//动态替换属性
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
//获取属性的一些信息
const char *property_getName(objc_property_t property)
const char *property_getAttributes(objc_property_t property)

方法相关

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
33
34
//获得一个实例方法、类方法
Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls, SEL name)

//方法实现相关操作
IMP class_getMethodImplementation(Class cls, SEL name)
IMP method_setImplementation(Method m, IMP imp)
void method_exchangeImplementations(Method m1, Method m2)

//拷贝方法列表(最后需要调用free释放)
Method *class_copyMethodList(Class cls, unsigned int *outCount)

//动态添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

//动态替换方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)

//获取方法的相关信息(带有copy的需要调用free去释放)
SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char *method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m)
char *method_copyReturnType(Method m)
char *method_copyArgumentType(Method m, unsigned int index)

//选择器相关
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str)

//用block作为方法实现
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)

具体应用

  1. 利用关联对象(AssociatedObject)给分类添加属性
  2. 遍历类的所有成员变量(修改textfield的占位文字颜色KVC、字典转模型、自动归档解档)
  3. hook 方法(交换系统的方法)
  4. 利用消息转发机制解决方法找不到的异常问题