不知道之前有没有小伙伴遇到过__NSFrozenArrayM相关的崩溃,比如 :
-[__NSFrozenArrayM addObjectsFromArray:]: unrecognized selector,
-[__NSFrozenArrayM objectAtIndexedSubscript:]: index 21 beyond bounds [0 .. 15]。
之前项目中遇到了关于__NSFrozenArrayM的崩溃,然后网上冲浪查找答案的时候,基本上都是答非所问,或者没有真正解决问题。比如像什么copy修饰可变数组就会导致这些问题什么的。日常代码书写的时候,即使是完全规范书写,没有发生这些“误操作”,也是会有__NSFrozenArrayM对象产生的。
先看一段代码演示:
NSMutableArray *mArray = [[NSMutableArray alloc] initWithCapacity:0];
for (int i = 0; i < 10; i++) {
[mArray addObject:@(i)];
NSArray *array = [[NSArray alloc] initWithArray:mArray];
NSLog(@"%d-%@", i, [array class]);
}
这里预期的结果是什么呢?照理说应该是第一个是__NSSingleObjectArrayI,后面都是__NSArrayI。我们来看下结果:
0-__NSSingleObjectArrayI
1-__NSArrayI
2-__NSArrayI
3-__NSArrayI
4-__NSArrayI
5-__NSFrozenArrayM
6-__NSFrozenArrayM
7-__NSFrozenArrayM
8-__NSFrozenArrayM
9-__NSFrozenArrayM
从序号5开始,类型从__NSArrayI变成了__NSFrozenArrayM。这里我猜测当可变数组的元素个数小于等于5的时候,可以快速构建不可变数组,所以没有生成中间类型__NSFrozenArrayM,当数量超过5个时候,就生成了中间数组__NSFrozenArrayM,如果对这款源码比较了解的小伙伴可以聊一聊哈。然后我也验证了一下,元素个数为几十个的场景下,也是__NSFrozenArrayM,应该就是以5为分界线了。
到这里,我们可以得出一个结论:当使用可变数组初始化不可变数组时,如果可变数组的元素个数超过5个,那么就会产生__NSFrozenArrayM类型对象。
读到这里,想必有些小伙伴也跟我一样,对其他情况下会产生什么现象产生好奇,那我们就挨个看一眼吧。
1.可变 -> 不可变
结论:当使用可变数组初始化不可变数组时,如果可变数组的元素个数超过5个,那么就会产生__NSFrozenArrayM类型对象。
2.可变 -> 可变
还是照例上代码:
NSMutableArray *mArray = [[NSMutableArray alloc] initWithCapacity:0];
for (int i = 0; i < 10; i++) {
[mArray addObject:@(i)];
NSMutableArray *array = [[NSMutableArray alloc] initWithArray:mArray];
NSLog(@"%d-%@", i, [array class]);
}
0-__NSArrayM
1-__NSArrayM
2-__NSArrayM
3-__NSArrayM
4-__NSArrayM
5-__NSArrayM
6-__NSArrayM
7-__NSArrayM
8-__NSArrayM
9-__NSArrayM
结论:可变数组初始化可变数组时,不会根据元素个数不同,产生__NSFrozenArrayM类型对象。
3.不可变 -> 可变
NSArray *sums = @[
@[@"0"],
@[@"0", @"0"],
@[@"0", @"0", @"0"],
@[@"0", @"0", @"0", @"0"],
@[@"0", @"0", @"0", @"0", @"0"],
@[@"0", @"0", @"0", @"0", @"0", @"0"],
@[@"0", @"0", @"0", @"0", @"0", @"0", @"0"],
@[@"0", @"0", @"0", @"0", @"0", @"0", @"0", @"0"],
@[@"0", @"0", @"0", @"0", @"0", @"0", @"0", @"0", @"0"],
@[@"0", @"0", @"0", @"0", @"0", @"0", @"0", @"0", @"0", @"0"]];
NSMutableArray *mArray = [[NSMutableArray alloc] initWithCapacity:0];
for (int i = 0; i < sums.count; i++) {
NSMutableArray *array = [[NSMutableArray alloc] initWithArray:sums[i]];
NSLog(@"%d-%@", i, [array class]);
}
0-__NSArrayM
1-__NSArrayM
2-__NSArrayM
3-__NSArrayM
4-__NSArrayM
5-__NSArrayM
6-__NSArrayM
7-__NSArrayM
8-__NSArrayM
9-__NSArrayM
结论:不可变数组初始化可变数组时,不会根据元素个数不同,产生__NSFrozenArrayM类型对象。
4.不可变 -> 不可变
NSArray *sums = @[
@[@"0"],
@[@"0", @"0"],
@[@"0", @"0", @"0"],
@[@"0", @"0", @"0", @"0"],
@[@"0", @"0", @"0", @"0", @"0"],
@[@"0", @"0", @"0", @"0", @"0", @"0"],
@[@"0", @"0", @"0", @"0", @"0", @"0", @"0"],
@[@"0", @"0", @"0", @"0", @"0", @"0", @"0", @"0"],
@[@"0", @"0", @"0", @"0", @"0", @"0", @"0", @"0", @"0"],
@[@"0", @"0", @"0", @"0", @"0", @"0", @"0", @"0", @"0", @"0"]];
NSMutableArray *mArray = [[NSMutableArray alloc] initWithCapacity:0];
for (int i = 0; i < sums.count; i++) {
NSArray *array = [[NSArray alloc] initWithArray:sums[i]];
NSLog(@"%d-%@", i, [array class]);
}
0-__NSSingleObjectArrayI
1-__NSArrayI
2-__NSArrayI
3-__NSArrayI
4-__NSArrayI
5-__NSArrayI
6-__NSArrayI
7-__NSArrayI
8-__NSArrayI
9-__NSArrayI
结论:不可变数组初始化不可变数组时,不会根据元素个数不同,产生__NSFrozenArrayM类型对象。
5.__NSFrozenArrayM -> 可变/不可变
NSArray *array = @[@"0", @"1", @"2", @"3", @"4", @"5"];
NSMutableArray *mArray = [[NSMutableArray alloc] initWithArray:array];
NSArray *frozenArray = [[NSArray alloc] initWithArray:mArray];
NSLog(@"%@", [frozenArray class]);
NSArray *arrayI = [[NSArray alloc] initWithArray:frozenArray];
NSLog(@"frozen初始化不可变数组:%@", [arrayI class]);
NSMutableArray *arrayM = [[NSMutableArray alloc] initWithArray:frozenArray];
NSLog(@"frozen初始化可变数组:%@", [arrayM class]);
__NSFrozenArrayM
frozen初始化不可变数组:__NSFrozenArrayM
frozen初始化可变数组:__NSArrayM
结论:__NSFrozenArrayM初始化不可变数组,生成的也是__NSFrozenArrayM,__NSFrozenArrayM初始化可变数组,生成的是可变数组。
6.后记
由上面这些实验数据我们不难看出,__NSFrozenArrayM虽然叫ArrayM,但是本质上是不可变类型的数组,那我们推测他应该是NSArray的类簇,所以,如果我们需要对__NSFrozenArrayM类型进行容错处理时,就需要写在NSArray的容错category里。这里我给到一个例子:
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[objc_getClass("__NSFrozenArrayM") swizzleMethod:@selector(objectAtIndexedSubscript:) withMethod:@selector(fObjectAtIndexedSubscript:)];
});
}
- (id)fObjectAtIndexedSubscript:(NSUInteger)index {
NSAssert([self count] > 0, ([NSString stringWithFormat:@"index %lu beyond bounds for empty array", (unsigned long)index]));
NSAssert(index < [self count], ([NSString stringWithFormat:@"index %lu beyond bounds [0...%lu]", (unsigned long)index, (unsigned long)[self count] - 1]));
if (index < [self count]) {
return [self fObjectAtIndexedSubscript:index];
}
return nil;
}