最近在面试,脸都被打肿了,下面分享一些脸被打肿的经历。
请听题
@property(copy, nonatomic) NSMutableArray *myFish;
self.myFish = [NSMutableArray array];
此时 self.myFish 的具体类型?
说来惭愧从来没有用过这个[NSMutableArray array]
api,但是根据我的直觉判断它可能返回的是NSMutableArray, 接下来就是赋值语句于是我想着[NSMutableArray copy]可能最终返回NSMutableArray。所以我给出的答案是NSMutableArray。
回答错误
回去老老实实看了下api
@interface NSArray (NSArrayCreation)
+ (instancetype)array;
@end
@interface NSObject
- (id)copy;
- (id)mutableCopy;
@end
首先, array
函数属于NSArray,那么NSMutableArray 作为NSArray的子类有没有重写这个方法呢?
NSMutableArray *p = [NSMutableArray array];
NSMutableArray *p1 = [p copy];
NSMutableArray *p2 = [p mutableCopy];
先写一段小代码,看看结果
所以了
- [NSMutableArray array] -> NSMutableArray
- [NSMutableArray copy] -> NSArray
- [NSMutableArray mutableCopy] -> NSMutableArray
继续探索一下 [NSMutableArray array] 为什么最终返回NSMutableArray,有两种可能
- NSMutableArray 重写了 + array方法
- 还是调用的 [NSAarray array],那么问题就会变成 + array为什么这么神奇。到底是怎么做到的。
再写段代码验证一下
Method arrayMethod = class_getClassMethod([NSArray class], @selector(array));
Method mutableArrayMethod = class_getClassMethod([NSMutableArray class], @selector(array));
IMP arrayMethodIMP = method_getImplementation(arrayMethod);
IMP mutableArrayMethodIMP = method_getImplementation(mutableArrayMethod);
assert(arrayMethod == mutableArrayMethod);
assert(arrayMethodIMP == mutableArrayMethodIMP);
assert 成功,这说明 [NSMutableArray array] 最终调用的是 [NSArray array]
当然稳当起见可以反向证明一下:
@interface NSMutableArray(array)
+ (instancetype)array;
@end
@implementation NSMutableArray(array)
+ (instancetype)array {
return [[NSArray array] mutableCopy];
}
@end
给NSMutableArray加个extension,最终导致断言失败,那么现在就要分析[NSArray array] 的魔力在哪里了,找了很久都没有找到Foundation的源码,如果谁知道哪里有下载,请告诉我。
先写个山寨版的构造函数。
@interface NSArray(Create)
+ (instancetype)createArray;
@end
@implementation NSArray(Create)
+ (instancetype)createArray {
return [[self alloc]init];
}
@end
这个createArray 可以实现和系统array一样的功能。
好了最后为了证明array,确实调用了init, 现在init方法进行hook
@implementation NSMutableArray (array)
/*
+load 方法会在类加入运行期调用,这是把方法调配放在+load方法里面的原因
*/
+ (void)load
{
static dispatch_once_t onceToken;
/*单例保证并行情况下不会有问题*/
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(init);
SEL swizzledSelector = @selector(customInit);
/*获取原来的方法*/
Method originalMethod = class_getInstanceMethod(class, originalSelector);
/*获取要替换的方法*/
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
});
}
- (instancetype)customInit {
// objc_msgSend()
return [self customInit];
}
@end
遗憾的是这个hook 并没有正常运行,而且引发了crash
造成了栈溢出,具体原因还需要研究一下,可能跟NSMutableArray本身是一个类簇有关系。
copy vs mutableCopy
两者的区别在于
一个调用copyfromZone
另一个调用mutableCopyFromZone
self.myFish = @[@0x1234];