参考链接:blog.sunnyxx.com/2014/12/18/…
我们都知道swizzle method在OC中是一种有趣又实用的技巧,现在假设我们想hook掉NSMutableArray的insertObject:atIndex:方法,我们会在NSMutableArray的分类中这样写:
swizzleInstanceMethod([self class], @selector(insertObject:atIndex:), @selector(swizzle_insertObject:atIndex:));
复制代码
然而我们会发现虽然swizzle成功了,但是实际上并没有执行swizzle_insertObject:atIndex:方法,这是为什么呢?
我们先写个测试代码:
NSString *nilStr = nil;
[[NSMutableArray array] addObject:nilStr];
复制代码
看下下面的错误信息:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
复制代码
__NSArrayM 是个什么鬼?这就是导致我们上面swizzle method后,swizzle_insertObject:atIndex:不执行的原因。
再来看以下的打印信息:
(lldb) po [[NSMutableArray alloc] init]
<__NSArrayM 0x7fb452e04a20>(
)
(lldb) po [NSMutableArray array]
<__NSArrayM 0x7fb452d0ba30>(
)
(lldb) po [NSMutableArray arrayWithObject:@""];
<__NSArrayM 0x7fb452d0baf0>(
)
(lldb) po [[NSMutableArray alloc] class]
__NSPlaceholderArray
(lldb) po [NSArray array]
<__NSArray0 0x7fb452d05910>(
)
(lldb) po [[NSArray alloc] init]
<__NSArray0 0x7fb452d05910>(
)
(lldb) po @[]
<__NSArray0 0x7fb452d05910>(
)
(lldb) po [[NSArray alloc] class]
__NSPlaceholderArray
(lldb) po [[NSArray alloc] initWithObjects:@"", nil];
<__NSArrayI 0x7fb452f059c0>(
)
(lldb)
复制代码
仔细看其中的端倪,
[NSMutableArray array] // __NSArrayM 类型(mutable)
[NSArray array] // __NSArray0 类型 (空 immutable)
[[NSArray alloc] initWithObjects:@"", nil] // __NSArrayI 类型(immutable)
[[NSArray alloc] class] // __NSPlaceholderArray
[[NSMutableArray alloc] class] // __NSPlaceholderArray
复制代码
可以看出,这些打印的类型是苹果隐藏起来的Array的实际类型,因此我们在文章开头的hook掉的是 [NSMutableArray class] 中的方法,并不是我们真正在项目中调用的 __NSArrayM 的 方法,所以并没有执行我们的swizzle_method。(有个小细节,3种方式打印的空不可变数组__NSArray0指向同一个地址)
那该怎么解决这个问题?方法也很简单,现在知道了源方法属于哪个类型,那么我们就对这个类型实用swizzleMethod:
NSMutableArray *mutArray = [NSMutableArray array];
swizzleInstanceMethod([mutArray class], @selector(insertObject:atIndex:), @selector(swizzle_insertObject:atIndex:));
复制代码
上面还有一个奇怪的类型 __NSPlaceholderArray,这个又是干嘛的,在上面的参考链接中有它的解释,我们这里讨论它和swizzle method有什么关联,其实如果我们想hook掉NSArray的 init 方法的话就需要用到它了,代码如下:
NSArray *placeholderArray = [NSArray alloc];
swizzleInstanceMethod([placeholderArray class], @selector(initWithObjects:count:), @selector(swizzle_initWithObjects:count:));
复制代码
不止 NSArray,在Foundation中这样的类簇还有 NSDictionary,NSString,NSNumber,所以如果想hook这些类型的方法,就要注意源方法实际属于哪个子类型上。