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:
__NSArrayI
Multiple elements__NSArray0
An empty array__NSSingleObjectArrayI
A 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)