background

Arr [0] = arr[0] = arr[0] = arr[0] But NOW I found that I did not write all, I was a big fool 😂, the point is THAT I also wrote the test code.

To solve

2019.11.22 update:

As reminded by @Madrid, hook is not recommended in order to ensure the robustness of the code. I quite agree with him. However, the ultimate purpose of hook is not to hide problems, but a way to avoid online crashes, while also ensuring the robustness of the usual code. Therefore, coupled with the DEBUG judgment, hook only in the case of RELEASE.

Hidden implementation classes

Starting with immutable arrays, there are three actual implementation classes:

  1. __NSArrayIMultiple elements
  2. __NSArray0An empty array
  3. __NSSingleObjectArrayIA single element

How do you get these implementation classes, if you write code, breakpoints and look at them

Methods exchange

Both class methods (class_getClassMethod) and object methods (class_getInstanceMethod) can be added, using object methods as an example

NSObject+Swizzling.h

+ (void)swizzlingInstanceMethodOrigSEL:(SEL)origSEL swizzleSEL:(SEL)swizzleSEL{
    Method origMe = class_getInstanceMethod(self, origSEL);
    Method swizzleMe = class_getInstanceMethod(self, swizzleSEL);
    
    // If the SEL and Method are not present, add the SEL and look at the result.
    BOOL addOrigMe = class_addMethod(self, origSEL, method_getImplementation(swizzleMe), method_getTypeEncoding(swizzleMe));
    
    // Add the original method successfully, indicating that the original method does not exist
    // Then swap the remaining half (SEL and Method association)
    if (addOrigMe) {
        class_replaceMethod(self, swizzleSEL, method_getImplementation(origMe), method_getTypeEncoding(origMe));
    }
    // Add the original Method failed, indicating that the original Method exists, can be replaced directly
    else{ method_exchangeImplementations(origMe, swizzleMe); }}Copy the code

With the previously found implementation class, you can determine that the array is out of bounds

#import "NSArray+Safe.h"

+ (void)load{

#ifdef DEBUG

#else
    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:)];
    });
#endif
}

#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];
}

Copy the code

Call yourself. Is that an infinite loop?

If you don’t look at the front, it is indeed an endless loop, only fools write 😂. Single_objectAtIndex is used as an example to simplify the implementation

__NSSingleObjectArrayI.single_objectAtIndex = __NSSingleObjectArrayI.objectAtIndex
Copy the code

Then single_objectAtIndex is called again inside single_objectAtIndex, which is equivalent to calling the original method objectAtIndex

There’s duplicate code. Can you stand it?

Single_objectAtIndex sf_objectAtIndex single_objectAtIndex sf_objectAtIndex The answer can endure! It really doesn’t fit together, and the reason is, after many swaps, you go back to the original method. For example, __NSSingleObjectArrayI, if written like this

[arraySingle swizzlingInstanceMethodOrigSEL:@selector(objectAtIndex:) swizzleSEL:@selector(sf_objectAtIndex:)];
[arraySingle swizzlingInstanceMethodOrigSEL:@selector(objectAtIndexedSubscript:) swizzleSEL:@selector(sf_objectAtIndexedSubscript:)];
Copy the code

Because __NSArrayI switched round

To simplify things, sf_objectAtIndex is currently implemented as __nsarrayi.objectatIndex

self.sf_objectAtIndex = __NSArrayI.objectAtIndex
Copy the code

If __NSSingleObjectArrayI swaps sf_objectAtIndex directly, the result is

__NSSingleObjectArrayI.objectAtIndex = __NSArrayI.objectAtIndex
Copy the code

So this time will crash, or honestly write each implementation class method.

An array variable

The implementation class has only one __NSArrayM, same as above

reference

Preventing array crashes in iOS development (updated)