2022-03-15 iOS OC常見崩潰和防止崩潰方案

崩潰方案:

JJException
AvoidCrash

用到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此方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容