0%

Swizzle的正确姿势(二)

上一篇文章介绍了Swizzle的错误姿势,错误地使用Swizzle很容易引发一系列隐蔽的问题,那么我们该如何正确且优雅地进行Swizzle呢?

在错误的Swizzle方案中,为了修改方法的实现,我们会新增一个方法(jy_viewDidLoad),然后和原始方法(viewDidLoad)进行交换。这个新增的方法其实没有什么作用,只是为了提供一个新的实现而已。

但是这个新增的方法却给代码带来了一系列的问题,比如上一篇文章提到的_cmd错误。那么能不能不新增这个方法呢?

当然是可以的。

方案

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
#import <objc/runtime.h>
#import <objc/message.h>

IMP class_swizzleSelector(Class clazz, SEL selector, IMP newImplementation)
{
// If the method does not exist for this class, do nothing
Method method = class_getInstanceMethod(clazz, selector);
if (! method) {
return NULL;
}

// Make sure the class implements the method. If this is not the case, inject an implementation, calling 'super'
const char *types = method_getTypeEncoding(method);
class_addMethod(clazz, selector, imp_implementationWithBlock(^(__unsafe_unretained id self, va_list argp) {
struct objc_super super = {
.receiver = self,
.super_class = class_getSuperclass(clazz)
};

id (*objc_msgSendSuper_typed)(struct objc_super *, SEL, va_list) = (void *)&objc_msgSendSuper;
return objc_msgSendSuper_typed(&super, selector, argp);
}), types);

// Can now safely swizzle
return class_replaceMethod(clazz, selector, newImplementation, types);
}

通过直接修改原有实现的方式,我们就可以做到不新增方法的Swizzle。

好像看着也不复杂?但实际情况并没有这么简单。虽然OC中大多数的方法调用都是使用objc_msgSend,但是毕竟是由例外的。当方法返回值是较大的结构体时,此时返回值无法直接放入寄存器中,编译器会转而使用objc_msgSend_stret方法,将返回值直接放入栈中。
这种情况下,上面的方案就不适用了,我们需要继续针对objc_msgSend_stret方法进行处理。

objc_msgSend_stret

话不多说,直接上代码:

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
IMP class_swizzleSelector_stret(Class clazz, SEL selector, IMP newImplementation)
{
// If the method does not exist for this class, do nothing
Method method = class_getInstanceMethod(clazz, selector);
if (! method) {
return NULL;
}

// Make sure the class implements the method. If this is not the case, inject an implementation, only calling 'super'
const char *types = method_getTypeEncoding(method);
class_addMethod(clazz, selector, imp_implementationWithBlock(^(__unsafe_unretained id self, va_list argp) {
struct objc_super super = {
.receiver = self,
.super_class = class_getSuperclass(clazz)
};

// Sufficiently large struct
typedef struct LargeStruct_ {
char dummy[16];
} LargeStruct;

// Cast the call to objc_msgSendSuper_stret appropriately
LargeStruct (*objc_msgSendSuper_stret_typed)(struct objc_super *, SEL, va_list) = (void *)&objc_msgSendSuper_stret;
return objc_msgSendSuper_stret_typed(&super, selector, argp);
}), types);

// Can now safely swizzle
return class_replaceMethod(clazz, selector, newImplementation, types);
}

这种方案虽然比直接新增方法并替换麻烦了一些,但是可以做到不新增方法实现Swizzle,也就自然不会有_cmd错误的问题。

这才是Swizzle的正确姿势!

参考