美文网首页设计模式
使用runtime拯救数组越界崩溃的思考

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

作者: Jack小麻雀_ | 来源:发表于2021-03-16 18:50 被阅读0次

    先提出三个问题,请各位思考:
    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请留言。

    相关文章

      网友评论

        本文标题:使用runtime拯救数组越界崩溃的思考

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