[iOS Runtime]数组越界写全了吗

背景

作了个升级检查,其中有一段代码直接下标访问的数组arr[0],我敢这样写,由于我用runtime判断了数组越界。可是我如今发现没写全,我真是个大傻子😂,关键是我还写了测试代码。数组

解决

隐藏的实现类

先说不可变数组,它的实际实现类有三种:bash

  1. __NSArrayI 多个元素
  2. __NSArray0 空数组
  3. __NSSingleObjectArrayI 单个元素

这些实现类怎么获得,写个代码,断点看下就明白了 测试

方法交换

类方法(class_getClassMethod)和对象方法(class_getInstanceMethod)均可以添加,这里以对象方法为例ui

NSObject+Swizzling.hspa

+ (void)swizzlingInstanceMethodOrigSEL:(SEL)origSEL swizzleSEL:(SEL)swizzleSEL{
    Method origMe = class_getInstanceMethod(self, origSEL);
    Method swizzleMe = class_getInstanceMethod(self, swizzleSEL);
    
    // 无论原方法存不存在,添加原方法看下结果, 这个只交换了一半(SEL和Method关联)
    BOOL addOrigMe = class_addMethod(self, origSEL, method_getImplementation(swizzleMe), method_getTypeEncoding(swizzleMe));
    
    // 添加原方法成功,说明原方法以前不存在
    // 而后交换剩下的一半(SEL和Method关联)
    if (addOrigMe) {
        class_replaceMethod(self, swizzleSEL, method_getImplementation(origMe), method_getTypeEncoding(origMe));
    }
    // 添加原方法失败,说明原方法以前是存在的,就能够直接替换Method
    else{
        method_exchangeImplementations(origMe, swizzleMe);
    }
}
复制代码

带上以前找到的实现类,就能够判断数组越界了code

#import "NSArray+Safe.h"cdn

+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        Class arrayI = objc_getClass("__NSArrayI");
        Class arrayEmpty = objc_getClass("__NSArray0");
        Class arraySingle = objc_getClass("__NSSingleObjectArrayI");
        
        [arrayI swizzlingInstanceMethodOrigSEL:@selector(objectAtIndex:) swizzleSEL:@selector(sf_objectAtIndex:)];
        [arrayI swizzlingInstanceMethodOrigSEL:@selector(objectAtIndexedSubscript:) swizzleSEL:@selector(sf_objectAtIndexedSubscript:)];

        [arrayEmpty swizzlingInstanceMethodOrigSEL:@selector(objectAtIndex:) swizzleSEL:@selector(empty_objectAtIndex:)];
        [arrayEmpty swizzlingInstanceMethodOrigSEL:@selector(objectAtIndexedSubscript:) swizzleSEL:@selector(empty_objectAtIndexedSubscript:)];

        [arraySingle swizzlingInstanceMethodOrigSEL:@selector(objectAtIndex:) swizzleSEL:@selector(single_objectAtIndex:)];
        [arraySingle swizzlingInstanceMethodOrigSEL:@selector(objectAtIndexedSubscript:) swizzleSEL:@selector(single_objectAtIndexedSubscript:)];
    });
}

#pragma mark - __NSArrayI

- (id)sf_objectAtIndex:(NSUInteger)index{

    if (index > self.count - 1) {
        return nil;
    }
    return [self sf_objectAtIndex:index];
}

- (id)sf_objectAtIndexedSubscript:(NSUInteger)idx{

    if (idx > self.count - 1) {
        return nil;
    }
    return [self sf_objectAtIndexedSubscript:idx];
}

#pragma mark - __NSArray0

- (id)empty_objectAtIndex:(NSUInteger)index{
    return nil;
}

- (id)empty_objectAtIndexedSubscript:(NSUInteger)idx{
    return nil;
}

#pragma mark - __NSSingleObjectArrayI

- (id)single_objectAtIndex:(NSUInteger)index{
    if (index > self.count - 1) {
        return nil;
    }
    return [self single_objectAtIndex:index];
}

- (id)single_objectAtIndexedSubscript:(NSUInteger)idx{
    if (idx > self.count - 1) {
        return nil;
    }
    return [self single_objectAtIndexedSubscript:idx];
}

复制代码

本身调用本身,死循环了吗?

若是不看前面,确实是死循环了,傻子才这样写😂。 可是前面交换了方法实现,以single_objectAtIndex为例子,简化一下就是对象

__NSSingleObjectArrayI.single_objectAtIndex = __NSSingleObjectArrayI.objectAtIndex
复制代码

那么在single_objectAtIndex方法内部再次调用single_objectAtIndex,其实至关于调用了原来的方法objectAtIndexblog

竟然有重复代码,能忍?

这里面,有重复代码,single_objectAtIndexsf_objectAtIndex,难道不能合在一块儿吗?答案能忍!是真不能合在一块儿,缘由是,屡次交换后回到了原来的方法。以__NSSingleObjectArrayI为例,若是写成这样ip

[arraySingle swizzlingInstanceMethodOrigSEL:@selector(objectAtIndex:) swizzleSEL:@selector(sf_objectAtIndex:)];
[arraySingle swizzlingInstanceMethodOrigSEL:@selector(objectAtIndexedSubscript:) swizzleSEL:@selector(sf_objectAtIndexedSubscript:)];
复制代码

由于__NSArrayI交换了一轮了

简化下大概像这样,sf_objectAtIndex目前的实现就是__NSArrayI.objectAtIndex

self.sf_objectAtIndex = __NSArrayI.objectAtIndex
复制代码

若是__NSSingleObjectArrayI直接交换sf_objectAtIndex,那么结果就是

__NSSingleObjectArrayI.objectAtIndex = __NSArrayI.objectAtIndex
复制代码

因此这个时候会崩溃,仍是老老实实的写每一个实现类的方法。

可变数组

实现类就只有一个__NSArrayM,实现同上

参考

iOS开发中防止数组越界致使的崩溃(升级版)

相关文章
相关标签/搜索