崩潰方案:
用到3個知識點
1、消息轉(zhuǎn)發(fā)機制
@implementation HelloClass
//這里沒啥用
-(BOOL)respondsToSelector:(SEL)aSelector{
bool a= [super respondsToSelector:aSelector];
return a;
}
//如果方法沒有實現(xiàn),默認返回false
//如果返回false,就會走消息轉(zhuǎn)發(fā)
+(BOOL)resolveInstanceMethod:(SEL)sel{
bool a = [super resolveInstanceMethod:sel];
return a;
}
//默認返回空
//又被稱為快速消息轉(zhuǎn)發(fā)。
// 如果為空,走慢速消息轉(zhuǎn)發(fā),繼續(xù)轉(zhuǎn)發(fā)消息
-(id)forwardingTargetForSelector:(SEL)aSelector{
id a = [super forwardingTargetForSelector:aSelector];
return a;
}
//默認實現(xiàn)是崩潰
//并且不能用try-catch捕獲
-(void)forwardInvocation:(NSInvocation *)anInvocation{
[super forwardInvocation:anInvocation];
NSLog(@"");
}
// 默認一般普通方法是返回空的。
// 如果是協(xié)議方法,沒有實現(xiàn),不會反回空。
//反回空,到這里就會崩潰了
//如果這里返回了簽名,會再次調(diào)用resolveInstanceMethod:(SEL)sel判斷是否實現(xiàn)
//如果仍然沒有實現(xiàn),就會走到fowardInvocation:
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSMethodSignature *a =[super methodSignatureForSelector:aSelector];
return a;
}
@end
2、關(guān)聯(lián)對象內(nèi)存管理
通過對象dealloc時,關(guān)聯(lián)對象也會dealloc的特性,可以給dealloc插入一些實現(xiàn)。
static const char DeallocNSObjectKey;
/**
Observer the target middle object
*/
@interface DeallocStub : NSObject
@property (nonatomic,readwrite,copy) void(^deallocBlock)(void);
@end
@implementation DeallocStub
- (void)dealloc {
if (self.deallocBlock) {
self.deallocBlock();
}
self.deallocBlock = nil;
}
@end
@implementation NSObject (DeallocBlock)
- (void)isd_deallocBlock:(void(^)(void))block{
@synchronized(self){
// 用數(shù)組, 保證每一個block都能執(zhí)行
NSMutableArray* blockArray = objc_getAssociatedObject(self, &DeallocNSObjectKey);
if (!blockArray) {
blockArray = [NSMutableArray array];
objc_setAssociatedObject(self, &DeallocNSObjectKey, blockArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
DeallocStub *stub = [DeallocStub new];
stub.deallocBlock = block;
[blockArray addObject:stub];
}
}
@end
3、HOOK方法交換
void isd_swizzleClassMethod(Class cls, SEL originSelector, SEL swizzleSelector)
{
if (!cls) {
return;
}
Method originalMethod = class_getClassMethod(cls, originSelector);
Method swizzledMethod = class_getClassMethod(cls, swizzleSelector);
Class metacls = objc_getMetaClass(NSStringFromClass(cls).UTF8String);
if (class_addMethod(metacls,
originSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod)) ) {
/* swizzing super class method, added if not exist */
class_replaceMethod(metacls,
swizzleSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
/* swizzleMethod maybe belong to super */
class_replaceMethod(metacls,
swizzleSelector,
class_replaceMethod(metacls,
originSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod)),
method_getTypeEncoding(originalMethod));
}
}
void isd_swizzleInstanceMethod(Class cls, SEL originSelector, SEL swizzleSelector)
{
if (!cls) {
return;
}
/* if current class not exist selector, then get super*/
Method originalMethod = class_getInstanceMethod(cls, originSelector);
Method swizzledMethod = class_getInstanceMethod(cls, swizzleSelector);
/* add selector if not exist, implement append with method */
//調(diào)用originSelector-》swizzledMethod
if (class_addMethod(cls,
originSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod)) ) {
/* replace class instance method, added if selector not exist */
/* for class cluster , it always add new selector here */
//調(diào)用swizzleSelector-》originalMethod
class_replaceMethod(cls,
swizzleSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
/* swizzleMethod maybe belong to super */
//swizzleSelector-》originalMethod
//originSelector-》swizzledMethod
class_replaceMethod(cls,
swizzleSelector,
class_replaceMethod(cls,
originSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod)),
method_getTypeEncoding(originalMethod));
}
}
崩潰和保護方案
1、nstimer。aTarget 類沒有實現(xiàn)對應的方法
hook NSTimer
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
方法。
2, nsnotfication奔潰, 沒有移除觀察者
觀察者dealloc釋放時候,沒有從消息中心移除自己
hook NSNotificationCenter
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;
方法。
給observer 對象添加關(guān)聯(lián)對象。
observer對象釋放-》 關(guān)聯(lián)對象在dealloc-〉綁定block,關(guān)聯(lián)對象dealloc時調(diào)用block,消息中心移除觀察者。
3, kvo崩潰
多移除: it is not registered as an observer.、
少移除都會崩潰:deallocated while key value observers were still registered with it
hook
addObserver:forKeyPath:options:context:
removeObserver:forKeyPath:
removeObserver:forKeyPath:context:
方法。
通過關(guān)聯(lián)對象。 記錄 已經(jīng)添加的 observer 和keypath。
4, 找不到方法崩潰
hook NSObject
methodSignatureForSelector:
forwardInvocation:
方法。
@implementation NSObject (UnrecognizedSelectorHook)
+ (void)xxx_swizzleUnrecognizedSelector{
xxx_swizzleInstanceMethod(self, @selector(methodSignatureForSelector:), @selector(methodSignatureForSelectorSwizzled:));
xxx_swizzleInstanceMethod(self, @selector(forwardInvocation:), @selector(forwardInvocationSwizzled:));
}
- (NSMethodSignature*)methodSignatureForSelectorSwizzled:(SEL)aSelector {
NSMethodSignature* methodSignature = [self methodSignatureForSelectorSwizzled:aSelector];
// 協(xié)議中的方法,但是沒有實現(xiàn), 默認返回非空
if (methodSignature) {
return methodSignature;
}
// 其他情況, 默認返回空
//原始的默認實現(xiàn)NSObject 函數(shù)指針
IMP originIMP = class_getMethodImplementation([NSObject class], @selector(methodSignatureForSelector:));
// 當前類自己的實現(xiàn)
IMP currentClassIMP = class_getMethodImplementation(self.class, @selector(methodSignatureForSelector:));
// If current class override methodSignatureForSelector return nil
//當前類自己重新實現(xiàn)了methodSignatureForSelector:方法;
if (originIMP != currentClassIMP){
return nil;
}
// Customer method signature
// void xxx(id,sel,id)
//隨便返回點啥簽名, 只要forwardInvocation被hook攔截住不調(diào)用就不會崩潰
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
- (void)forwardInvocationSwizzled:(NSInvocation*)invocation{
//forwardInvocation:
NSString* message = [NSString stringWithFormat:@"Unrecognized selector class:%@ and selector:%@",NSStringFromClass(self.class),NSStringFromSelector(invocation.selector)];
handleCrashException(xxxCrashProtectionUnrecognizedSelector,message,@{});
}
@end
還有其他方案:
HOOK
NSNULL、 NSObject、NSNumber、NSString
NSNULL類的 forwardingTargetForSelector:方法
比如對NSNumber調(diào)用NSString的方法,可以forwardingTargetForSelector:返回NSString對象。
NSString也一樣, 原對象轉(zhuǎn)化NSNumber對象返回。
NSNull @"", @[], @{}, @0 respondsToSelector判斷,字符串、數(shù)組、字典、number哪個實現(xiàn)了原來selector,就返回對應固定對象,保證不崩潰。
NSObject實現(xiàn)為,forwardingTargetForSelector:轉(zhuǎn)發(fā)給新對象。新的類動態(tài)使用
class_addMethod 添加實現(xiàn)。 IMP指向一個C函數(shù)。
5、 集合類,nsstring,nsdictionary,nsarray 以及他們的可變版本 插入nil,超過下標越界訪問崩潰
6、NSUserDefaults崩潰。 key 或者value 有一個為NULL都會崩潰的
hook NSUserDefaults 類:
objectForKey:
setObject:forKey:
方法。
7、野指針崩潰
hook
dealloc
方法。
就是dealloc 之后,給當前實例對象通過
objc_destructInstance(self);
object_setClass(self, [xxxZombieSub class]);
方法,修改isa,讓其所有方法,都轉(zhuǎn)向重新定向的類
在新類的
- (id)forwardingTargetForSelector:(SEL)selector 。
轉(zhuǎn)發(fā)
-》
void unrecognizedSelectorZombie(ZombieSelectorHandle* self, SEL _cmd){
// fromObject 對象內(nèi)存free后,仍然會成為野指針
NSString* message = [NSString stringWithFormat:@"unrecognizedSelectorZombie:%@ selector:%@",[self.fromObject class],NSStringFromSelector(_cmd)];
handleCrashException(xxxCrashProtectionZombie,message,@{});
NSLog(@"%@", message);
}
8、非主線程操作UI
hook
UIView的下面方法
(&onceToken, ^{
NSDictionary * methods = @{@"setNeedsLayout": @"xxxSafe_setNeedsLayout",
@"setNeedsDisplay": @"xxxSafe_setNeedsDisplay",
@"setNeedsDisplayInRect:": @"xxxSafe_setNeedsDisplayInRect:",
@"addSubview:": @"xxxSafe_addSubview:",
@"removeFromSuperview:": @"xxxSafe_removeFromSuperview:",
};
方法
9、其他崩潰
NSJSONSerialization
JSONObjectWithData:options:error:
JSON序列化 data是nil
HOOK NSJSONSerialization此方法。