设计模式

使用runtime拯救数组越界崩溃的思考

2021-03-16  本文已影响0人  Jack小麻雀_

先提出三个问题,请各位思考:
1.如何使用runtime防止数组崩溃?
2.iOS中的类簇;
3.方法交换method_exchangeImplementations为什么在+load中调用,如果在+initialize中调用会怎样?


面试时经常会被问到,你在项目里使用过runtime吗?我相信绝大多数的开发者都想反问面试官,你说说你都用runtime做了什么我听听,给我看看代码你是不是真的用了runtime在项目里。
言归正传,下面聊聊如何通过runtime的method_exchangeImplementations方法实现对array增加保护:

以NSMutableArray为例,需要处理:
查询方法2;
插入方法
1;
删除方法*1;
先看一下代码实现:

//NSMutableArray+Safe.h
@interface NSMutableArray (Safe)
@end

//NSMutableArray+Safe.m
#import "NSMutableArray+Safe.h"
#import <objc/runtime.h>

@implementation NSMutableArray (Safe)
+ (void)initialize{
    //防止子类未实现+initialize造成多次调用
    if (self == [NSMutableArray class]) {
        //objectAtIndex
        Method m_objcAtIndex = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(objectAtIndex:));
        Method m_safeObjcAtIndex = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(jk_safeGetObjectAtIndex:));
        method_exchangeImplementations(m_objcAtIndex, m_safeObjcAtIndex);
        
        //[index]
        Method m_quick_objcAtIndex = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(objectAtIndexedSubscript:));
        Method m_quick_safeObjcAtIndex = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(jk_safeGetObjectAtIndexedSubscript:));
        method_exchangeImplementations(m_quick_objcAtIndex, m_quick_safeObjcAtIndex);
        
        //insertObjectAtIndex
        Method m_insetObjcAtIndex = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(insertObject:atIndex:));
        Method m_safeInsertObjectAtIndex = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(jk_safeInsertObject:atIndex:));
        method_exchangeImplementations(m_insetObjcAtIndex, m_safeInsertObjectAtIndex);
        
        //removeObjectAtIndex
        Method m_removeObjcAtIndex = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(removeObjectAtIndex:));
        Method m_safeRemoveObjectAtIndex = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(jk_safeRemoveAtIndex:));
        method_exchangeImplementations(m_removeObjcAtIndex, m_safeRemoveObjectAtIndex);
    }
}

//objectAtIndex:index
- (id)jk_safeGetObjectAtIndex:(NSInteger)index{
    if (index >= self.count || index < 0) {
        NSLog(@"index out of range");
        return nil;
    }
    return [self jk_safeGetObjectAtIndex:index];
}

//[index]
- (id)jk_safeGetObjectAtIndexedSubscript:(NSInteger)index{
    if (index >= self.count || index < 0) {
        NSLog(@"[index] out of range");
        return nil;
    }
    return [self jk_safeGetObjectAtIndexedSubscript: index];
}

//insertObject:objc atIndex:index
- (void)jk_safeInsertObject:(id)object atIndex:(NSInteger)index{
    if (!object) {
        NSLog(@"insertObject is nil");
        return;
    }
    if (index > self.count || index < 0 ) {
        NSLog(@"insertIndex = %ld out of range",(long)index);
        return;
    }
    [self jk_safeInsertObject:object atIndex:index];
}

//removeAtIndex:index
- (void)jk_safeRemoveAtIndex:(NSInteger)index{
    if (self.count <= 0) {
        NSLog(@"array is empty, fail to removeAtIndex:%ld",index);
        return;
    }
    if (index >= self.count || index < 0 ) {
        NSLog(@"fail to remove index = %ld out of range",(long)index);
        return;
    }
    [self jk_safeRemoveAtIndex:index];
}

@end

逻辑比较简单,应该轻易就能理解开发者对防治数组越界所做的处理。下面将讨论第二个问题,类簇。


class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name),第一个参数需要传入类对象,处理NSMutableArray为什么不用[NSMutableArray class],而是通过NSClassFromString(@“__NSArrayM”)获取到的类对象,那么这个__NSArrayM是什么?

因为NSArray,NSMutableArray在Foundation框架中设计成类簇,我们使用array时实际上是系统“偷偷”帮我们在用它们的子类,通过代码看一下:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSArray *arr0 = [[NSArray alloc] init];
    NSArray *arr1 = @[@"1"];
    NSArray *arr2 = @[@"1",@"2"];
    NSMutableArray *m_arr0 = [[NSMutableArray alloc] initWithArray:arr0];
    NSMutableArray *m_arr1 = [[NSMutableArray alloc] initWithArray:arr1];
    NSMutableArray *m_arr2 = [[NSMutableArray alloc] initWithArray:arr2];
    
    NSLog(@"arr0: %@",[arr0 class]);
    NSLog(@"arr1: %@",[arr1 class]);
    NSLog(@"arr2: %@",[arr2 class]);
    NSLog(@"m_arr0: %@",[m_arr0 class]);
    NSLog(@"m_arr1: %@",[m_arr1 class]);
    NSLog(@"m_arr2: %@",[m_arr2 class]);
        
    return YES;
}

//运行后的输出结果
2021-03-16 00:00:18.438239+0800 CrashCatcherDemo[1414:57908] arr0: __NSArray0
2021-03-16 00:00:18.438364+0800 CrashCatcherDemo[1414:57908] arr1: __NSSingleObjectArrayI
2021-03-16 00:00:18.438464+0800 CrashCatcherDemo[1414:57908] arr2: __NSArrayI
2021-03-16 00:00:18.438570+0800 CrashCatcherDemo[1414:57908] m_arr0: __NSArrayM
2021-03-16 00:00:18.438686+0800 CrashCatcherDemo[1414:57908] m_arr1: __NSArrayM
2021-03-16 00:00:18.438783+0800 CrashCatcherDemo[1414:57908] m_arr2: __NSArrayM

由此可见使用NSArray时我们其实在用的是__NSArray0(array中没有元素)、__NSSingleObjectArrayI(array中只有一个元素)、__NSArrayI(array中有多个元素),而NSMutableArray其实是__NSArrayM,我想此时关于为什么类对象由NSClassFromString(@"__NSArrayM")获取的原因已经明了。


让我们开始第三点,方法交换在+load 和 +initialize进行方法交换有什么区别?
首先我们得先了解这两个方法有什么区别,iOS app启动后进入main函数前会完成所有类的+load方法,+initialize会在该类第一次使用的时候被调用,类似于的懒加载,但是需要注意如果子类没有实现自己的+initialize方法会造成父类+initialize方法被多次调用(为什么会被多次调用而+(void)load不会被多次调用可以看这个demo,https://gitee.com/Jack_1993/load_initialize_demo),所以处理前需要判断,还有方法交换要保证只交换了一(单数)次,如果还不放心的话可以使用dispatch_once保证方法交换只被执行一次,代码供大家参考:

+ (void)initialize{
    if (self == [NSArray class]) {
        //safe already...
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            //do the thing you want...
        });
    }
}

分享结束,祝愿大家(还有我)面试顺利找到自己心仪的工作,如果需要demo请留言。

上一篇下一篇

猜你喜欢

热点阅读