内容简介:由于Objective-c是Message机制,而且对象在转换的时候,会有拿到的对象和预期不一致,所以会有方法找不到的情况,在找不到方法时,查找方法将会进入方法Forward流程,系统给了三次补救的机会,所以我们要解决这个问题,在这三次均可以解决这个问题如果resolveInstanceMethod没有处理,将进行到forwardingTargetForSelector这步来,这时候你可以返回nil,你也可以用一个Stub对象来接住,把消息流程流转到了你的Stub那边了,然后在你的Stub里添加不存在的S
Unrecognized Selector Sent to Instance
由于Objective-c是Message机制,而且对象在转换的时候,会有拿到的对象和预期不一致,所以会有方法找不到的情况,在找不到方法时,查找方法将会进入方法Forward流程,系统给了三次补救的机会,所以我们要解决这个问题,在这三次均可以解决这个问题
-
resolveInstanceMethod:(SEL)sel 这是实例化方法没有找到方法,最先执行的函数,首先会流转到这里来,返回值是BOOL,没有找到就是NO,找到就返回YES,如果要解决就需要再当前的实例中加入不存在的Selector,并绑定IMP,示例如下:
static void xxxInstanceName(id self, SEL cmd, id value) { NSLog(@"resolveInstanceMethod %@", value); } + (BOOL)resolveInstanceMethod:(SEL)sel { NSLog(@"resolveInstanceMethod"); NSMethodSignature* sign = [self methodSignatureForSelector:selector]; if (!sign) { class_addMethod([self class], sel, (IMP)xxxInstanceName, "v@:@"); return YES; } return [super resolveInstanceMethod:sel]; }
-
forwardingTargetForSelector:(SEL)aSelector
如果resolveInstanceMethod没有处理,将进行到forwardingTargetForSelector这步来,这时候你可以返回nil,你也可以用一个Stub对象来接住,把消息流程流转到了你的Stub那边了,然后在你的Stub里添加不存在的Selector,这样就不会crash了,示例如下:
- (id)forwardingTargetForSelectorSwizzled:(SEL)selector{ NSMethodSignature* sign = [self methodSignatureForSelector:selector]; if (!sign) { id stub = [[UnrecognizedSelectorHandle new] autorelease]; class_addMethod([stub class], selector, (IMP)unrecognizedSelector, "v@:"); return stub; } return [self forwardingTargetForSelectorSwizzled:selector]; }
-
methodSignatureForSelector:(SEL)aSelector
-
forwardInvocation:(NSInvocation *)anInvocation
这两个方法一起说,因为他们之间有关联,
-
当methodSignatureForSelector返回nil时,会Crash
-
如果methodSignatureForSelector返回一个定义好的NSMethodSignature,但是没有实现forwardInvocation,也会闪退,如果实现了forwardInvocation, 会先返回到resolveInstanceMethod然后再才会到forwardInvocation
-
当流转到
forwardInvocation
,通过以下方法:
[anInvocation invokeWithTarget:xxxtarget1]; [anInvocation invokeWithTarget:xxxtarget2];
还可以流转到多个对象,[anInvocation invokeWithTarget:xxxtarget2]是为了让不存在的方法有着陆点
-
doesNotRecognizeSelector:(SEL)aSelector 执行到这里的时候,两种情况:
-
当methodSignatureForSelector返回一种任意的方法签名的时候,也会进入doesNotRecognizeSelector,但是不会闪退
-
当methodSignatureForSelector返回nil时,进入doesNotRecognizeSelector就会闪退
根据以上流程,最终还是选择流程2,原因如下:
-
resolveInstanceMethod虽然可以解决问题,给不存在的方法增加到示例中去,会污染当前示例
-
forwardInvocation在三步中式最后一步,会导致流转的周期变长,而且会产生NSInvocation,性能不是最好的选择
如何监听实例化对象什么时候释放
先说下这个知识点,因为在接下来的好几个地方都会用到,会有一些异常的情况,所以需要一种知道当前创建者啥时候释放,首先会想到dealloc,这样会Hook的NSObject,在一定程度会影响性能,后面发现一种比较优雅的方法,原理来自于Runtime源码:
/*********************************************************************** * objc_destructInstance * Destroys an instance without freeing memory. * Calls C++ destructors. * Calls ARR ivar cleanup. * Removes associative references. * Returns `obj`. Does nothing if `obj` is nil. * Be warned that GC DOES NOT CALL THIS. If you edit this, also edit finalize. * CoreFoundation and other clients do call this under GC. **********************************************************************/ void *objc_destructInstance(id obj) { if (obj) { // Read all of the flags at once for performance. bool cxx = obj->hasCxxDtor(); bool assoc = !UseGC && obj->hasAssociatedObjects(); bool dealloc = !UseGC; // This order is important. if (cxx) object_cxxDestruct(obj); if (assoc) _object_remove_assocations(obj); if (dealloc) obj->clearDeallocating(); } return obj; }
_object_remove_assocations
会释放所有的用AssociatedObject数据。
objc_setAssociatedObject
给当前对象添加一个中间对象,当前对象释放时,会清理AssociatedObject数据,AssociatedObject的中间对象将被清理释放,中间对象的dealloc方法将被执行。
最终清理被遗漏的监听者,会用在KVO和NSNotification清理没用的监听者,不过这种方式有以下问题需要注意:
-
清理的时候线程安全问题
-
清理时机偏晚,是否适合你当前的情况
NSArray,NSMutableArray,NSDictonary,NSMutableDictionary
-
类族(Class Cluster)
NSDictonary,NSArray,NSString等,都使用了类族,这种模式最大的好处就是,可以隐藏抽象基类背后的复杂细节,使用者只需调用基类简单的方法就可以返回不同的子类实例
-
Swizzle Hook
这里就不赘述Swizzle概念了,Google到处都是讲解的,这里给一个典型的例子:
swizzleInstanceMethod(NSClassFromString(@"__NSArrayI"), @selector(objectAtIndex:), @selector(hookObjectAtIndex:)); - (id) hookObjectAtIndex:(NSUInteger)index { if (index < self.count) { return [self hookObjectAtIndex:index]; } handleCrashException(@"HookObjectAtIndex invalid index"); return nil; }
Zombie Pointer
让野指针不闪退是模仿了XCode debug的Zombie Object,也参考了网易和美团的做法,主要是以下步骤:
-
Hook住dealloc方法
-
如果当前示例在黑名单里,就把当年前示例加入集合,并把当前对象
objc_destructInstance
清理引用关系,并未真正释放内存,并将object_setClass
设置成自己的中间对象 -
Hook中间对象的方法,收到的消息都由中间对象来处理
-
维护的野指针集合,要么根据个数来维护,要么根据总大小来维护,当满了,就需要真正释放对象内存
free(obj)
存在的问题:
-
需要单独的内存那些问题对象
-
最后释放内存后,再访问时会闪退,这个方法只是一定程度延迟了闪退时间
-
需要后台维护黑名单机制,来指定那些问题对象
KVO
KVO在以下情况会导致闪退:
-
添加监听后没有清除会导致闪退
-
清除不存在的key也会闪退
-
添加重复的key导致闪退
需要Hook以下方法:
-
addObserver:forKeyPath:options:context:
-
removeObserver:forKeyPath:
主要解决以下问题:
-
在注册监听后,中间对象需要维护注册的数据集合,当宿主释放时,清除还在集合中的监听者
-
保护key不存在的情况
-
保护重复添加的情况
NSTimer
NSTimer存在以下问题:
-
Target是强引用,内存泄漏
-
在宿主不存在的时候,清理NSTimer
Hook以下方法:
-
scheduledTimerWithTimeInterval:target:selector:userInfo:repeats
解决方法: 1.当repeats为NO时,走原始方法 2.当repeats为YES时,新建一个对象,声明一个target属性为weak类型,指向参数的target,当中间对象的target为空时,清理NSTimer
NSNotification
NSNotification的主要问题是:
-
添加通知后,没有移除导致Crash的问题(不过在iOS9以后没有这个问题,我在真机8.3测试也没有这个问题,不知道iOS8是否有这个问题)
Hook以下方法:
-
addObserver:selector:name:object:
原因和解决办法: 问题就在在于和assign和weak问题,野指针问题,要么置空指针或者删除空指针的集合
MRC
这里单独说下,为什么工程选择了MRC,因为在Hook集合类型的时候,启动的时候就闪退了,Crash的地方在系统类里,Stack里显示在CF这层,这里只能猜测系统底层对ARC的支持不好导致的,后续改成MRC就没有问题,所以这个需要继续研究和追踪,如果有知道的同学记得告知我下
性能
本来是没有打算注意性能这个问题的,因为从Hook原理的角度来说,只是交换IMP的指向,时间复杂度来说,只是在系统级别上增加了几条逻辑判断指令,所以这个影响是极小的,基本可以忽略,我经过测试,循环1000000次,没有HOOK和HOOK相差0.0x秒的,所以减少Crash,来增加这么点时间复杂度来说,是值得的。
不过最后说一点,就是dealloc确实需要注意,因为这里存在集合的操作,所以要注意时间复杂度,dealloc执行的很频繁的,而且主线程和子线程都会涉及到,尤其是主线程一定注意,否则会影响到UI的体验。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
决战618:探秘京东技术取胜之道
京东集团618作战指挥中心 / 电子工业出版社 / 2017-11 / 99
《决战618:探秘京东技术取胜之道》以京东技术团队备战618为主线,集合京东数百位技术专家,对京东所有和618相关的关键技术系统进行了一次全面的梳理和总结,是京东技术体系的智慧结晶。 《决战618:探秘京东技术取胜之道》从前端的网站、移动入口到后端的结算、履约、物流、供应链等体系,系统展示了京东最新的技术成就。同时,也涵盖了京东正在充分运用大数据、人工智能等先进技术对所有技术体系架构进行整体......一起来看看 《决战618:探秘京东技术取胜之道》 这本书的介绍吧!