IOS 降低线上版本Crash率

作者: 02ec002ab1b2 | 来源:发表于2017-07-11 17:10 被阅读312次

    IOS 防止Crash 组件WTSafeGuard

    背景

    由于Object-C本身的不安全性,导致很容易产生Crash。在这些Crash,很多我们可以利用自定义手段,进行避免。这样可以降低线上版本的Crash率,提升用户
    体验。WTSafeGuard 避免APP Crash 组件,目前能做到的还很有限。

    UIKit Called on Non-Main Thread

    UIKit不是线程安全的,执行UIKit操作如果不在主线程很可能造成程序Crash。所以我们对Hook,UIView 的setNeedsLayout,layoutIfNeeded,layoutSubviews,setNeedsUpdateConstraints方法。如果执行以上函数没有在主队列,通过强行将执行代码,在主队列执行。

    - (void)wt_safe_setNeedsLayout
    {
        if(![NSThread isMainThread]){
            dispatch_async(dispatch_get_main_queue(), ^{
                NSAssert(false, @"wt_safe_setNeedsLayout failed");
                [self wt_safe_setNeedsLayout];
            });
        }else{
            [self wt_safe_setNeedsLayout];
        }
    }
    
    - (void)wt_safe_layoutIfNeeded
    {
        if(![NSThread isMainThread]){
            dispatch_async(dispatch_get_main_queue(), ^{
                NSAssert(false, @"wt_safe_layoutIfNeeded failed");
                [self wt_safe_layoutIfNeeded];
            });
        }else{
            [self wt_safe_layoutIfNeeded];
        }
    }
    
    - (void)wt_safe_layoutSubviews
    {
        if(![NSThread isMainThread]){
            dispatch_async(dispatch_get_main_queue(), ^{
                NSAssert(false, @"wt_safe_layoutSubviews failed");
                [self wt_safe_layoutSubviews];
            });
        }else{
            [self wt_safe_layoutSubviews];
        }
    }
    
    - (void)wt_safe_setNeedsUpdateConstraints
    {
        if(![NSThread isMainThread]){
            dispatch_async(dispatch_get_main_queue(), ^{
                NSAssert(false, @"wt_safe_setNeedsUpdateConstraints failed");
                [self wt_safe_setNeedsUpdateConstraints];
            });
        }else{
            [self wt_safe_setNeedsUpdateConstraints];
        }
    }
    
    

    避免 Foundation 类Carsh

    NSString

    + (instancetype)stringWithUTF8String:(const char *)bytes
    - (instancetype)initWithString:(NSString *)aString
    - (instancetype)initWithUTF8String:(const char *)nullTerminatedCString
    - (instancetype)initWithFormat:(NSString *)format locale:(id)locale arguments:(va_list)argList
    - (NSString *)stringByAppendingString:(NSString *)aString
    - (unichar)characterAtIndex:(NSUInteger)index
    - (void)getCharacters:(unichar *)buffer range:(NSRange)range
    - (NSRange)rangeOfCharacterFromSet:(NSCharacterSet  *)searchSet 
                                options:(NSStringCompareOptions)mask
                                  range:(NSRange)searchRange
                                        
    - (NSRange)rangeOfString:(NSString *)searchString
                    options:(NSStringCompareOptions)mask
                      range:(NSRange)searchRange
                     locale:(NSLocale *)locale
    - (NSString *)substringFromIndex:(NSUInteger)from
    - (NSString *)substringWithRange:(NSRange)range
    - (NSString *)substringToIndex:(NSUInteger)to
    - (void)getLineStart:(NSUInteger *)startPtr   
                     end:(NSUInteger *)lineEndPtr                                         
                     contentsEnd:(NSUInteger *)contentsEndPtr
                       forRange:(NSRange)range
    

    NSAttributedString

    hook 方法:对传入参数range 进行check,如果range有问题,直接返回nil

    - (NSAttributedString *)attributedSubstringFromRange:(NSRange)range;
    
    

    NSFileManager

    - (nullable NSDirectoryEnumerator<NSURL *> *)enumeratorAtURL:(NSURL *)url includingPropertiesForKeys:(nullable NSArray<NSURLResourceKey> *)keys options:(NSDirectoryEnumerationOptions)mask errorHandler:(nullable BOOL (^)(NSURL *url, NSError *error))handler
    

    NSIndexPath

    - (void)getIndexes:(NSUInteger *)indexes range:(NSRange)positionRang
    

    NSJSONSerialization

    + (NSData *)dataWithJSONObject:(id)obj options:(NSJSONWritingOptions)opt error:(NSError **)error
    

    NSDictionary

    hook 方法:

    + (id)sharedKeySetForKeys:(NSArray<KeyType <NSCopying>> *)keys
    - (instancetype)initWithObjects:(const ObjectType _Nonnull [_Nullable])objects forKeys:(const KeyType <NSCopying> _Nonnull [_Nullable])keys
    - (instancetype)initWithObjects:(const ObjectType _Nonnull [_Nullable])objects forKeys:(const KeyType <NSCopying> _Nonnull [_Nullable])keys count:(NSUInteger)cnt
    

    NSMutableDictionary

    hook 方法:

    + (NSMutableDictionary<KeyType, ObjectType> *)dictionaryWithSharedKeySet:(id)keyset
    - (void)setObject:(ObjectType)anObject forKey:(KeyType <NSCopying>)aKey;
    - (void)removeObjectForKey:(KeyType)aKey;
    

    NSSet

    - (instancetype)WT_initWithObjects:(const id [])objects count:(NSUInteger)cnt
    - (void)addObject:(id)object;
    - (void)makeObjectsPerformSelector:(SEL)aSelector
    - (void)makeObjectsPerformSelector:(SEL)aSelector
                            withObject:(id)argument
    

    NSMutableSet

    - (void)addObject:(id)anObject
    

    NSMutableString

    - (void)setString:(NSString *)aString
    - (void)appendString:(NSString *)aString
    - (void)deleteCharactersInRange:(NSRange)range
    - (void)insertString:(NSString *)aString atIndex:(NSUInteger)loc
    - (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)aString
    -  (NSUInteger)replaceOccurrencesOfString:(NSString *)target
                                         withString:(NSString *)replacement
                                            options:(NSStringCompareOptions)options
                                              range:(NSRange)searchRange
    

    NSURL

    + (NSURL *)fileURLWithPath:(NSString *)path
    + (NSURL *)fileURLWithPath:(NSString *)path isDirectory:(BOOL)isDir
    + (NSURL *)fileURLWithPathComponents:(NSArray<NSString *> *)components
    + (NSURL *)fileURLWithPath:(NSString *)path
                          isDirectory:(BOOL)isDir
                        relativeToURL:(NSURL *)baseURL
    - (instancetype)initWithString:(NSString *)URLString relativeToURL:(NSURL *)baseURL
    - (instancetype)initFileURLWithPath:(NSString *)path
    - (instancetype)initFileURLWithPath:(NSString *)path
                                 relativeToURL:(NSURL *)baseURL
    - (instancetype)initFileURLWithPath:(NSString *)path
                                   isDirectory:(BOOL)isDir
                                 relativeToURL:(NSURL *)baseURL
    
    1. KVO
    2. 容器越界(NSArray, NSDictionary,...)
    3. unrecognized selector crash (这个很多时候是由于class使用错误导致)
    4. NSTimer 导致crash

    KVO Crash

    项目中KVO crash 占比很高, 主要原因为,添加删除不对称导致。
    解决方法为,添加Map进行缓存。
    不过这个方案,目前还有缺陷。

    unrecognized selector crash

    这个就比较简单了,直接上代码:

        [NSObject jr_swizzleMethod:@selector(forwardingTargetForSelector:) withMethod:@selector(WT_safeForwardingTargetForSelector:) error:&error];
        
        - (id)WT_safeForwardingTargetForSelector:(SEL)aSelector
    {
        NSMethodSignature *signature = [self methodSignatureForSelector:aSelector];
        if ([self respondsToSelector:aSelector] || signature) {
            return [self WT_safeForwardingTargetForSelector:aSelector];
        }
        
        return [WTSafeGuard createFakeForwardTargetObject:self selector:aSelector];
    }
    

    相关文章

      网友评论

      • _moses:NSMutableDictionary分类中写的有问题,用两个系统方法和同一个自定义方法交换,那这个自定义方法起不到任何作用,实际上是交换了两个系统方法。
      • 0d84922e336a:看了下内部实现,这个苹果审核已经对objc_getClass("__NSArrayI") 这样的方案进行了拒绝通过了,
        02ec002ab1b2:还好,我们开发中用到的很少。这个组建就是起到hook异常情况
        0d84922e336a:@我爱水果 是的,不行了,我们大概花了两个迭代来处理这个事情,很恶心,还好现在线上基本崩溃率很低,所以苹果昨天被人举报搞垄断
        02ec002ab1b2:这样都不行了!苹果太狠了

      本文标题:IOS 降低线上版本Crash率

      本文链接:https://www.haomeiwen.com/subject/mvemhxtx.html