基础知识iOS开发常用知识点

iOS-字典防崩溃机制(runtime)

2018-01-31  本文已影响221人  woniu
在写项目的过程中难免会遇到后台开发者返回的字典中有nil的扯淡问题,前面的文章中有提到过一个第三方Dictionary+NilSafe的处理机制可以自动去除掉字典中有nil的key/value,以防止崩溃。正好Dictionary+NilSafe有runtime的应用场景以及相关知识点,所以Dictionary+NilSafe就成为了学习runtime实际应用的一个很好的剖析对象,剖析demo会在文末呈给各位看官。还有,如果本人有说错的地方还希望各位能指出来。好了,下面我们开始吧:

一、进入到Dictionary+NilSafe类别的.m文件中,我们设置方法交换类别NSObject (Swizzling),再此处处理相关的方法交换。

#pragma mark  设置通用的交换方法
@implementation NSObject (Swizzling)

#pragma mark 进行方法的交换
+ (BOOL)gl_swizzleMethod:(SEL)origSel withMethod:(SEL)altSel {
    //1、获取原生的方法和我们要交换的方法
    Method origMethod = class_getInstanceMethod(self, origSel);
    Method altMethod = class_getInstanceMethod(self, altSel);
    //2、如果两个方法有一个不存在返回NO
    if (!origMethod || !altMethod) {
        return NO;
    }
    //3、添加方法  若已经存在会添加失败
   BOOL ori = class_addMethod(self,
                    origSel,
                    class_getMethodImplementation(self, origSel),
                    method_getTypeEncoding(origMethod));
    NSLog(@"原方法添加:%@",ori?@"yes":@"no");

   BOOL alt = class_addMethod(self,
                    altSel,
                    class_getMethodImplementation(self, altSel),
                    method_getTypeEncoding(altMethod));
    
    NSLog(@"新方法添加:%@",alt?@"yes":@"no");
    //4、交换方法的实现
    method_exchangeImplementations(class_getInstanceMethod(self, origSel),
                                   class_getInstanceMethod(self, altSel));
    return YES;
}
//交换的类方法
+ (BOOL)gl_swizzleClassMethod:(SEL)origSel withMethod:(SEL)altSel {
    return [object_getClass((id)self) gl_swizzleMethod:origSel withMethod:altSel];
}

@end

二、针对不可变字典我们还是在load里面交换法方法,然后对字典的两种初始化方法(类、对象方法)进行处理,遍历出所有的key/value并判断是否为nil,并过滤掉为nil的key/value,然后返回正确的数据。详细剖析如下:

#pragma mark 设置不可变字典的崩溃处理
@implementation NSDictionary (NilSafe)

//我们还是在load方法中实现保证一开始就被加载,dispatch_once保证在多线程下的安全执行。
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //获取原生方法以及设置需要交换的方法  两种初始化方式都涵盖到,防止因为涵盖不周到导致的崩溃。
        [self gl_swizzleMethod:@selector(initWithObjects:forKeys:count:) withMethod:@selector(gl_initWithObjects:forKeys:count:)];

        [self gl_swizzleClassMethod:@selector(dictionaryWithObjects:forKeys:count:) withMethod:@selector(gl_dictionaryWithObjects:forKeys:count:)];
    });
}
//字典 类方法的调用,取出所有的key和value来进行比对,安顿是否有为空
+ (instancetype)gl_dictionaryWithObjects:(const id [])objects forKeys:(const id<NSCopying> [])keys count:(NSUInteger)cnt {
    id safeObjects[cnt];
    id safeKeys[cnt];
    NSUInteger j = 0;
    for (NSUInteger i = 0; i < cnt; i++) {
        id key = keys[i];
        id obj = objects[i];
        //如果key或value有为空的情况,就跳过去
        if (!key || !obj) {
            /*
             break是结束整个循环,而continue是结束本次循环(跳过下一步),
             为了循环的继续,我们就必须选择continue.
             */
            continue;
        }
        //每一个value对应一个key,这个是相互对应的,详见demo。
        safeKeys[j] = key;
        safeObjects[j] = obj;
        j++;
    }
    //处理完毕之后,我们返回新的kay、value以及count,此时我们已经将nil的key&value清除掉了。
    return [self gl_dictionaryWithObjects:safeObjects forKeys:safeKeys count:j];
}

//在这里对数据进行重组,针对数据为空的情况,处理方式同上
- (instancetype)gl_initWithObjects:(const id [])objects forKeys:(const id<NSCopying> [])keys count:(NSUInteger)cnt {
    id safeObjects[cnt];
    id safeKeys[cnt];
    NSUInteger j = 0;
    for (NSUInteger i = 0; i < cnt; i++) {
        id key = keys[i];
        id obj = objects[i];
        if (!key || !obj) {
            continue;
        }
        if (!obj) {
            obj = [NSNull null];
        }
        safeKeys[j] = key;
        safeObjects[j] = obj;
        j++;
    }
    return [self gl_initWithObjects:safeObjects forKeys:safeKeys count:j];
}
@end

三、可变字典这里我必须要好好说道说道了,可变字典的添加数据的方式有两种,一是通过类方法直接添加一个不可变字典,二是初始化后添加key/value。

划重点啦:可变字典的处理机制和不可变数组的机制不同,在可变数组中判断key/value为nil之后return的时候,开始的时候推测一次性遍历所有的key/value,遇到数据nil的就跳出来。でも(呆毛),这样处理的话会导致nil后面的正常数据无法返回从而丢失数据。所以,由此可以推断,可变数组中是每次只一对key/value进行监测的,然后遇到nil的数据就return,然后再判断下一对数据,这样nil的数据就会被被过滤掉了。
类方法初始化,直接添加一个不可变字典。
NSMutableDictionary *mudic = [NSMutableDictionary dictionaryWithDictionary:dic];
对象方法初始化
@interface NSMutableDictionary<KeyType, ObjectType> : NSDictionary<KeyType, ObjectType>

- (void)removeObjectForKey:(KeyType)aKey;
- (void)setObject:(ObjectType)anObject forKey:(KeyType <NSCopying>)aKey;
- (instancetype)init NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithCapacity:(NSUInteger)numItems NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;

@end

@interface NSMutableDictionary<KeyType, ObjectType> (NSExtendedMutableDictionary)

- (void)addEntriesFromDictionary:(NSDictionary<KeyType, ObjectType> *)otherDictionary;
- (void)removeAllObjects;
- (void)removeObjectsForKeys:(NSArray<KeyType> *)keyArray;
- (void)setDictionary:(NSDictionary<KeyType, ObjectType> *)otherDictionary;
- (void)setObject:(nullable ObjectType)obj forKeyedSubscript:(KeyType <NSCopying>)key API_AVAILABLE(macos(10.8), ios(6.0), watchos(2.0), tvos(9.0));

@end
#pragma mark 设置可变字典的崩溃处理
@implementation NSMutableDictionary (NilSafe)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //获取可变字典的类名。调用方法进行交换
        Class class = NSClassFromString(@"__NSDictionaryM");
        [class gl_swizzleMethod:@selector(setObject:forKey:) withMethod:@selector(gl_setObject:forKey:)];
        //为什么要使用下面的这个方法呢?
        [class gl_swizzleMethod:@selector(setObject:forKeyedSubscript:) withMethod:@selector(gl_setObject:forKeyedSubscript:)];
    });
}
//为什么在这里不讲顺序呢,这里直接使用teturn不会导致前期遍历到而导致返回,后面的数据
//int i;
- (void)gl_setObject:(id)anObject forKey:(id<NSCopying>)aKey {
    //疑问:字典里面就几个数据,但是在这里执行了几百次,我是百思不得姐,希望有朝一日能解开这个千古谜团。
//    NSLog(@"可变数组到底调用执行了几次呢:%d",i++);
    if (!aKey || !anObject) {
        //结束整个函数,这里调用的数据是每次都已调用一次,这里如果改成coutinue�呢? やめで ,必须要用到遍历循环中才可以使用。
        //如果这里我不做return处理呢,测试结果是崩溃,由此可以推断,可变数组中的数据是每次一对key/value进行监测的,然后遇到nil的数据就return,这样就不会返回nil的数据,相当于被过滤掉了。
        NSLog(@"遇到为nil的情况,执行一次");
        return;
    }
    [self gl_setObject:anObject forKey:aKey];
}

- (void)gl_setObject:(id)obj forKeyedSubscript:(id<NSCopying>)key {
//    NSLog(@"~~~~~~~可变数组到底调用执行了几次呢:%d",i++);
    if (!key || !obj) {
        return;
    }
    [self gl_setObject:obj forKeyedSubscript:key];
}
@end

奉上demo:https://github.com/caiqingchong/NSDictionaryNilTest

上一篇下一篇

猜你喜欢

热点阅读