兼容字典、数组 常用方法的 Crash

1,122 阅读3分钟

这里主要针对数组和字典的常用 API,使用不规范会引起 Crash 的 API 进行容错处理,具体兼容的 API 如下所示:

//兼容:NSArray 的方法
- (ObjectType)objectAtIndex:(NSUInteger)index;
- (ObjectType)objectAtIndexedSubscript:(NSUInteger)idx API_AVAILABLE(macos(10.8), ios(6.0), watchos(2.0), tvos(9.0));

//兼容:NSMutableArray 的方法
 - (ObjectType)objectAtIndex:(NSUInteger)index;
 - (ObjectType)objectAtIndexedSubscript:(NSUInteger)idx API_AVAILABLE(macos(10.8), ios(6.0), watchos(2.0), tvos(9.0));
 - (void)replaceObjectAtIndex:(NSUInteger)index withObject:(ObjectType)anObject;
 - (void)addObject:(ObjectType)anObject;
- (void)insertObject:(ObjectType)anObject atIndex:(NSUInteger)index;
- (void)removeObjectAtIndex:(NSUInteger)index;
- (void)removeObjectsInRange:(NSRange)range;

//兼容 NSDictionary 的方法
+ (instancetype)dictionaryWithObjects:(NSArray<ObjectType> *)objects forKeys:(NSArray<KeyType <NSCopying>> *)keys;
+ (instancetype)dictionaryWithObjects:(const ObjectType _Nonnull [_Nullable])objects forKeys:(const KeyType <NSCopying> _Nonnull [_Nullable])keys count:(NSUInteger)cnt;

//兼容 NSMutableDictionary 的方法
- (void)setObject:(ObjectType)anObject forKey:(KeyType <NSCopying>)aKey;

这里为了方便,直接把 .m 文件中的全部代码,贡献给大家,需要的人可以自取,创建一个集成自 NSObject 的类,把里面的内容 Copy 到自己的类中,这里是直接使用分类处理,在运行时会自动查找父类方法列表中的方法,所以直接把代码 Copy 到类中就可以,不需要做其它操作。

//
//  KKSafeCollection.m
//  KKTest
//
//  Created by FumiTech on 2020/8/11.
//  Copyright © 2020 ZKKBP. All rights reserved.
//

#import "KKSafeCollection.h"
#import <objc/runtime.h>
#import <UIKit/UIDevice.h>

#pragma mark - Exception Log
#define exceptionLog(...) privateLog(__VA_ARGS__)
void privateLog(NSString *fmt, ...) NS_FORMAT_FUNCTION(1, 2);
void privateLog(NSString *fmt, ...) {
#ifdef DEBUG
     va_list ap;
     va_start(ap, fmt);
     NSString *content = [[NSString alloc] initWithFormat:fmt arguments:ap];
     NSLog(@"%@", content);
     va_end(ap);
     
     NSLog(@" ============= call stack ========== \n%@", [NSThread callStackSymbols]);
#endif
}

#pragma mark - NSArray
@interface NSArray (Safe)

@end

@implementation NSArray (Safe)

+ (Method)instanceMethodOfSelector:(SEL)selector {
    return class_getInstanceMethod(NSClassFromString(@"__NSArrayI"), selector);
}

+ (Method)classMethodOfSelector:(SEL)selector {
    return class_getClassMethod([self class], selector);
}

/// 兼容获取对象越界
- (id)kk_objectAtIndexI:(NSUInteger)index {
    @autoreleasepool {
        NSUInteger count = self.count;
        if (index >= count) {
            exceptionLog(@"[%@ %@] index {%zd} beyond bounds [0...%zd]", NSStringFromClass([self class]), NSStringFromSelector(_cmd), index, MAX(count - 1, 0));
            return nil;
        }

        return [self kk_objectAtIndexI:index];
    }
}

/// 兼容下标的方式获取对象越界;array[3]
- (id)kk_objectAtIndexedSubscriptI:(NSUInteger)index {
    @autoreleasepool {
        NSUInteger count = self.count;
        if (index >= count) {
            exceptionLog(@"[%@ %@] index {%zd} beyond bounds [0...%zd]", NSStringFromClass([self class]), NSStringFromSelector(_cmd), index, MAX(count - 1, 0));
            return nil;
        }

        return [self kk_objectAtIndexedSubscriptI:index];
    }
}

@end


#pragma mark - NSMutableArray
@interface NSMutableArray (Safe)

@end

@implementation NSMutableArray (Safe)

+ (Method)instanceMethodOfSelector:(SEL)selector {
    return class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), selector);
}

/// 兼容获取对象越界
- (id)kk_objectAtIndexM:(NSUInteger)index {
    @autoreleasepool {
        NSUInteger count = self.count;
        if (index >= count) {
            exceptionLog(@"[%@ %@] index {%zd} beyond bounds [0...%zd]", NSStringFromClass([self class]), NSStringFromSelector(_cmd), index, MAX(count - 1, 0));
            return nil;
        }

        return [self kk_objectAtIndexM:index];
    }
}

/// 兼容下标的方式获取对象越界;mutableArray[3]
- (id)kk_objectAtIndexedSubscriptM:(NSUInteger)index {
    @autoreleasepool {
        NSUInteger count = self.count;
        if (index >= count) {
            exceptionLog(@"[%@ %@] index {%zd} beyond bounds [0...%zd]", NSStringFromClass([self class]), NSStringFromSelector(_cmd), index, MAX(count - 1, 0));
            return nil;
        }
        return [self kk_objectAtIndexedSubscriptM:index];
    }
}

///兼容添加空对象
- (void)kk_addObject:(id)anObject {
    if (!anObject) {
        exceptionLog(@"[%@ %@], nil object.", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
        return;
    }
    [self kk_addObject:anObject];
}


/// 兼容替换的对象为空,或者替换的 index 越界
- (void)kk_replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject {
    NSUInteger count = self.count;
    if (index >= count) {
        exceptionLog(@"[%@ %@] index {%zd} beyond bounds [0...%zd].", NSStringFromClass([self class]), NSStringFromSelector(_cmd), index, MAX(count - 1, 0));
        return;
    }
    
    if (!anObject) {
        exceptionLog(@"[%@ %@] nil object.", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
        return;
    }
    
    [self kk_replaceObjectAtIndex:index withObject:anObject];
}

/// 兼容插入的对象为空,或者插入的 index 越界
- (void)kk_insertObject:(id)anObject atIndex:(NSUInteger)index {
    NSUInteger count = self.count;
    if (index > count) {
        exceptionLog(@"[%@ %@] index {%zd} beyond bounds [0...%zd].", NSStringFromClass([self class]), NSStringFromSelector(_cmd), index, MAX(count - 1, 0));
        return;
    }
    
    if (!anObject) {
        exceptionLog(@"[%@ %@] nil object.", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
        return;
    }
    
    [self kk_insertObject:anObject atIndex:index];
}

/// 兼容删除的 index 越界
- (void)kk_removeObjectAtIndex:(NSUInteger)index {
    NSUInteger count = self.count;
    if (index >= count) {
        exceptionLog(@"[%@ %@] index {%zd} beyond bounds [0...%zd].", NSStringFromClass([self class]), NSStringFromSelector(_cmd), index, MAX(count - 1, 0));
        return;
    }
    
    [self kk_removeObjectAtIndex:index];
}

/// 兼容删除的 range 越界
- (void)kk_removeObjectsInRange:(NSRange)range {
    NSUInteger count = self.count;
    if ((range.location + range.length) > count) {
        exceptionLog(@"[%@ %@] range {%zd} beyond bounds [0...%zd].", NSStringFromClass([self class]), NSStringFromSelector(_cmd), index, MAX(count - 1, 0));
        return;
    }
    
    [self kk_removeObjectsInRange:range];
}

@end


#pragma mark - NSDictionary
@interface NSDictionary (Safe)

@end

@implementation NSDictionary (Safe)

+ (Method)classMethodOfSelector:(SEL)selector {
    return class_getClassMethod([self class], selector);
}

/// 兼容 count 内,object 和 key 数量不同,或者有空对象
+ (instancetype)kk_dictionaryWithObjects:(const id[])objects forKeys:(const id<NSCopying>[])keys count:(NSUInteger)count {
    id validObjects[count];
    id<NSCopying> validKeys[count];
    NSUInteger index = 0;
    for (NSUInteger i = 0; i < count; i++) {
        if (objects[i] && keys[i]) {
            validObjects[index] = objects[i];
            validKeys[index] = keys[i];
            index++;
        } else {
            exceptionLog(@"[%@ %@] nil object or key at index{%zd}.", NSStringFromClass(self), NSStringFromSelector(_cmd), i);
        }
    }
    
    return [self kk_dictionaryWithObjects:validObjects                                  forKeys:validKeys                                    count:index];
}

/// 兼容 object 和 key 不良不一样,或者有空对象
+ (instancetype)kk_dictionaryWithObjects:(NSArray *)objects forKeys:(const NSArray<NSCopying> *)keys {
    NSMutableArray *arrsObject = [NSMutableArray array];
    NSMutableArray *arrsKey = [NSMutableArray array];
    NSUInteger count = MIN(objects.count, keys.count);
    for (NSUInteger i = 0; i < count; i++) {
       if (objects[i] && keys[i]) {
           [arrsObject addObject:objects[i]];
           [arrsKey addObject:keys[i]];
        } else {
            exceptionLog(@"[%@ %@] nil object or key at index{%zd}.", NSStringFromClass(self), NSStringFromSelector(_cmd), i);
        }
    }
    return [self kk_dictionaryWithObjects:arrsObject                                  forKeys:arrsKey];
}

@end


#pragma mark - NSMutableDictionary
@interface NSMutableDictionary (Safe)

@end

@implementation NSMutableDictionary (Safe)

+ (Method)instanceMethodOfSelector:(SEL)selector {
    return class_getInstanceMethod(NSClassFromString(@"__NSDictionaryM"), selector);
}

/// 兼容添加 key 或者 object 为空
- (void)kk_setObject:(id)object forKey:(id<NSCopying>)aKey {
    if (!aKey) {
        exceptionLog(@"[%@ %@] nil key.", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
        return;
    }
    if (!object) {
        exceptionLog(@"[%@ %@] nil object.", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
        return;
    }
    
    [self kk_setObject:object forKey:aKey];
}

@end


#pragma mark - KKSafeCollection
@implementation KKSafeCollection

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 不可变数组
        [self exchangeOriginalMethod:[NSArray instanceMethodOfSelector:@selector(objectAtIndex:)]
                           newMethod:[NSArray instanceMethodOfSelector:@selector(kk_objectAtIndexI:)]];
        
        [self exchangeOriginalMethod:[NSArray instanceMethodOfSelector:@selector(objectAtIndexedSubscript:)]
                           newMethod:[NSArray instanceMethodOfSelector:@selector(kk_objectAtIndexedSubscriptI:)]];
        
        // 可变数组
        [self exchangeOriginalMethod:[NSMutableArray instanceMethodOfSelector:@selector(objectAtIndex:)]
                           newMethod:[NSMutableArray instanceMethodOfSelector:@selector(kk_objectAtIndexM:)]];
        
        if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 11.0) {
            [self exchangeOriginalMethod:[NSMutableArray instanceMethodOfSelector:@selector(objectAtIndexedSubscript:)]
                               newMethod:[NSMutableArray instanceMethodOfSelector:@selector(kk_objectAtIndexedSubscriptM:)]];
        }
        
        [self exchangeOriginalMethod:[NSMutableArray instanceMethodOfSelector:@selector(replaceObjectAtIndex:withObject:)]
                           newMethod:[NSMutableArray instanceMethodOfSelector:@selector(kk_replaceObjectAtIndex:withObject:)]];
        
        [self exchangeOriginalMethod:[NSMutableArray instanceMethodOfSelector:@selector(addObject:)]
                           newMethod:[NSMutableArray instanceMethodOfSelector:@selector(kk_addObject:)]];
        
        [self exchangeOriginalMethod:[NSMutableArray instanceMethodOfSelector:@selector(insertObject:atIndex:)]
                           newMethod:[NSMutableArray instanceMethodOfSelector:@selector(kk_insertObject:atIndex:)]];
        
        [self exchangeOriginalMethod:[NSMutableArray instanceMethodOfSelector:@selector(removeObjectAtIndex:)]
                           newMethod:[NSMutableArray instanceMethodOfSelector:@selector(kk_removeObjectAtIndex:)]];
        
        [self exchangeOriginalMethod:[NSMutableArray instanceMethodOfSelector:@selector(removeObjectsInRange:)]
                           newMethod:[NSMutableArray instanceMethodOfSelector:@selector(kk_removeObjectsInRange:)]];
        
        // 不可变字典
        [self exchangeOriginalMethod:[NSDictionary classMethodOfSelector:@selector(dictionaryWithObjects:forKeys:count:)]
                           newMethod:[NSDictionary classMethodOfSelector:@selector(kk_dictionaryWithObjects:forKeys:count:)]];
        
        [self exchangeOriginalMethod:[NSDictionary classMethodOfSelector:@selector(dictionaryWithObjects:forKeys:)]
                           newMethod:[NSDictionary classMethodOfSelector:@selector(kk_dictionaryWithObjects:forKeys:)]];
        
        // 可变字典
        [self exchangeOriginalMethod:[NSMutableDictionary instanceMethodOfSelector:@selector(setObject:forKey:)]
                           newMethod:[NSMutableDictionary instanceMethodOfSelector:@selector(kk_setObject:forKey:)]];
    });
}

#pragma mark - Private Method
+ (void)exchangeOriginalMethod:(Method)originalMethod newMethod:(Method)newMethod {
    method_exchangeImplementations(originalMethod, newMethod);
}

@end