[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開發中防止數組越界致使的崩潰(升級版)

相關文章
相關標籤/搜索