runtime MethodSwizzle 实践之 奇怪crash : [UIKeyboardLayoutStar release]: message sent to deallocated instance
情景: 使用MethodSwizzle 实现对数组、字典 等系统方法的安全校验。显然能达到预期效果,但实际发现当
键盘显示的情况下 home app 进入后台,再单击app 图标 切换回前台时 发生crash :
[UIKeyboardLayoutStar release]: message sent to deallocated instance
UIKeyboardLayoutStar 是键盘上的布局的视图吧,
整个工程都在ARC下 构建,很奇怪,而且必须。
信息:
其中都有提到DurexKit 原理都是一样的,上面提到原因是替换了 NSArray的objectAtIndex: 方法,
不过在我的项目原因是替换了NSMutableArray 的objectAtIndex:( NSMutableArray和 NSArray 的objectAtIndex:都有替换,单独替换 NSArray 的objectAtIndex:方法则不会引起crash)
解决方案:给 添加非ARC 支持,并改写实现
有提到:。。貌似 arc 有时也不一定可靠。
----------------------2015.3.23-----更新--0.0--继续填坑啊----------------------------------
话说使用 语法糖初始化 数组和 字典 真的好方便。。。@{....} @[...] 但是很容易埋雷。。大多数里面存的都是变量 在代码里。so , 也需要校验 。
最初使用 MethodSwizzle 很嗨皮啊,首先涉及可变参数在替换的方法里 试图使用NSInvocation 来解决 传递多个参数的问题,最后 莫名crash 。。你可以试试。
再说说数组和字典里的类簇(工厂模式):
http://blog.sunnyxx.com/2014/12/18/class-cluster/
后来发现如果你用语法糖 初始化数组 crash 信息如下:
'*** -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from xx
解决方案使用静态方法 + objc_msgSend 来实现:
#import <objc/message.h> #define CurrentClass objc_getClass("__NSArrayI") static id array_safe_initWithObjects(id self, SEL _cmd, const id* objects,unsigned int count){ id orignialResult = nil; @try { orignialResult = objc_msgSend(self, @selector(array_safe_initWithObjects:count:),objects,count); } @catch (NSException *exception) { DDLogDebug(@"=__NSPlaceholderArray===BUSTED!"); } return orignialResult ; } @implementation NSArray (SafeCheck) +(void)load{ //添加 [CurrentClass swizzleInstanceSelector:@selector(objectAtIndex:) withNewSelector:@selector(safeObjectsAtIndex:)]; [objc_getClass("__NSPlaceholderArray") swizzleSelector: @selector(initWithObjects:count:) withNewSelector:@selector(array_safe_initWithObjects:count:) andNewIMP:(IMP)&array_safe_initWithObjects]; }
+ (void) swizzleSelector:(SEL)originalSelector withNewSelector:(SEL)newSelector andNewIMP:(IMP)imp{ Method originMethod = class_getInstanceMethod(self, originalSelector); const char * methodEncodeType = method_getTypeEncoding(originMethod); BOOL methodAdded = class_addMethod(self, newSelector, imp, methodEncodeType); if (methodAdded) { Method newMethod = class_getInstanceMethod(self,newSelector); method_exchangeImplementations(newMethod, originMethod); }else{ DDLogDebug(@"=====faile="); } }
在运行时中会维护 selector 和 IMP 对应关系表。
同理字典也一样呢可以使用这种方法 添加语法糖校验。
#import <objc/message.h> static id dic_safe_initWithObjects(id self, SEL _cmd, const id* objects, const id* keys, unsigned int count) { id orignialResult = nil; @try { orignialResult = objc_msgSend(self, @selector(dic_safe_initWithObjects:forKeys:count:), objects, keys, count); } @catch (NSException *exception) { DDLogDebug(@"__NSPlaceholderDictionary===BUSTED!"); } return orignialResult; } @implementation NSDictionary (SafeCheck) +(void)load{ [objc_getClass("__NSPlaceholderDictionary") swizzleSelector:@selector(initWithObjects:forKeys:count:) withNewSelector:@selector(dic_safe_initWithObjects:forKeys:count:) andNewIMP:(IMP)&dic_safe_initWithObjects]; }