参考链接:
我们都知道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这些类型的方法,就要注意源方法实际属于哪个子类型上。