崩溃方案:
用到3个知识点
1、消息转发机制
@implementation HelloClass
//这里没啥用
-(BOOL)respondsToSelector:(SEL)aSelector{
bool a= [super respondsToSelector:aSelector];
return a;
}
//如果方法没有实现,默认返回false
//如果返回false,就会走消息转发
+(BOOL)resolveInstanceMethod:(SEL)sel{
bool a = [super resolveInstanceMethod:sel];
return a;
}
//默认返回空
//又被称为快速消息转发。
// 如果为空,走慢速消息转发,继续转发消息
-(id)forwardingTargetForSelector:(SEL)aSelector{
id a = [super forwardingTargetForSelector:aSelector];
return a;
}
//默认实现是崩溃
//并且不能用try-catch捕获
-(void)forwardInvocation:(NSInvocation *)anInvocation{
[super forwardInvocation:anInvocation];
NSLog(@"");
}
// 默认一般普通方法是返回空的。
// 如果是协议方法,没有实现,不会反回空。
//反回空,到这里就会崩溃了
//如果这里返回了签名,会再次调用resolveInstanceMethod:(SEL)sel判断是否实现
//如果仍然没有实现,就会走到fowardInvocation:
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSMethodSignature *a =[super methodSignatureForSelector:aSelector];
return a;
}
@end
2、关联对象内存管理
通过对象dealloc时,关联对象也会dealloc的特性,可以给dealloc插入一些实现。
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){
// 用数组, 保证每一个block都能执行
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 */
//调用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 */
//调用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 类没有实现对应的方法
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 对象添加关联对象。
observer对象释放-》 关联对象在dealloc-〉绑定block,关联对象dealloc时调用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:
方法。
通过关联对象。 记录 已经添加的 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];
// 协议中的方法,但是没有实现, 默认返回非空
if (methodSignature) {
return methodSignature;
}
// 其他情况, 默认返回空
//原始的默认实现NSObject 函数指针
IMP originIMP = class_getMethodImplementation([NSObject class], @selector(methodSignatureForSelector:));
// 当前类自己的实现
IMP currentClassIMP = class_getMethodImplementation(self.class, @selector(methodSignatureForSelector:));
// If current class override methodSignatureForSelector return nil
//当前类自己重新实现了methodSignatureForSelector:方法;
if (originIMP != currentClassIMP){
return nil;
}
// Customer method signature
// void xxx(id,sel,id)
//随便返回点啥签名, 只要forwardInvocation被hook拦截住不调用就不会崩溃
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调用NSString的方法,可以forwardingTargetForSelector:返回NSString对象。
NSString也一样, 原对象转化NSNumber对象返回。
NSNull @"", @[], @{}, @0 respondsToSelector判断,字符串、数组、字典、number哪个实现了原来selector,就返回对应固定对象,保证不崩溃。
NSObject实现为,forwardingTargetForSelector:转发给新对象。新的类动态使用
class_addMethod 添加实现。 IMP指向一个C函数。
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,让其所有方法,都转向重新定向的类
在新类的
- (id)forwardingTargetForSelector:(SEL)selector 。
转发
-》
void unrecognizedSelectorZombie(ZombieSelectorHandle* self, SEL _cmd){
// fromObject 对象内存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此方法。