先提出三个问题,请各位思考:
1.如何使用runtime防止数组崩溃?
2.iOS中的类簇;
3.方法交换method_exchangeImplementations为什么在+load中调用,如果在+initialize中调用会怎样?
面试时经常会被问到,你在项目里使用过runtime吗?我相信绝大多数的开发者都想反问面试官,你说说你都用runtime做了什么我听听,给我看看代码你是不是真的用了runtime在项目里。
言归正传,下面聊聊如何通过runtime的method_exchangeImplementations方法实现对array增加保护:
以NSMutableArray为例,需要处理:
查询方法2;
插入方法1;
删除方法*1;
先看一下代码实现:
//NSMutableArray+Safe.h
@interface NSMutableArray (Safe)
@end
//NSMutableArray+Safe.m
#import "NSMutableArray+Safe.h"
#import <objc/runtime.h>
@implementation NSMutableArray (Safe)
+ (void)initialize{
//防止子类未实现+initialize造成多次调用
if (self == [NSMutableArray class]) {
//objectAtIndex
Method m_objcAtIndex = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(objectAtIndex:));
Method m_safeObjcAtIndex = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(jk_safeGetObjectAtIndex:));
method_exchangeImplementations(m_objcAtIndex, m_safeObjcAtIndex);
//[index]
Method m_quick_objcAtIndex = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(objectAtIndexedSubscript:));
Method m_quick_safeObjcAtIndex = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(jk_safeGetObjectAtIndexedSubscript:));
method_exchangeImplementations(m_quick_objcAtIndex, m_quick_safeObjcAtIndex);
//insertObjectAtIndex
Method m_insetObjcAtIndex = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(insertObject:atIndex:));
Method m_safeInsertObjectAtIndex = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(jk_safeInsertObject:atIndex:));
method_exchangeImplementations(m_insetObjcAtIndex, m_safeInsertObjectAtIndex);
//removeObjectAtIndex
Method m_removeObjcAtIndex = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(removeObjectAtIndex:));
Method m_safeRemoveObjectAtIndex = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(jk_safeRemoveAtIndex:));
method_exchangeImplementations(m_removeObjcAtIndex, m_safeRemoveObjectAtIndex);
}
}
//objectAtIndex:index
- (id)jk_safeGetObjectAtIndex:(NSInteger)index{
if (index >= self.count || index < 0) {
NSLog(@"index out of range");
return nil;
}
return [self jk_safeGetObjectAtIndex:index];
}
//[index]
- (id)jk_safeGetObjectAtIndexedSubscript:(NSInteger)index{
if (index >= self.count || index < 0) {
NSLog(@"[index] out of range");
return nil;
}
return [self jk_safeGetObjectAtIndexedSubscript: index];
}
//insertObject:objc atIndex:index
- (void)jk_safeInsertObject:(id)object atIndex:(NSInteger)index{
if (!object) {
NSLog(@"insertObject is nil");
return;
}
if (index > self.count || index < 0 ) {
NSLog(@"insertIndex = %ld out of range",(long)index);
return;
}
[self jk_safeInsertObject:object atIndex:index];
}
//removeAtIndex:index
- (void)jk_safeRemoveAtIndex:(NSInteger)index{
if (self.count <= 0) {
NSLog(@"array is empty, fail to removeAtIndex:%ld",index);
return;
}
if (index >= self.count || index < 0 ) {
NSLog(@"fail to remove index = %ld out of range",(long)index);
return;
}
[self jk_safeRemoveAtIndex:index];
}
@end
逻辑比较简单,应该轻易就能理解开发者对防治数组越界所做的处理。下面将讨论第二个问题,类簇。
class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name),第一个参数需要传入类对象,处理NSMutableArray为什么不用[NSMutableArray class],而是通过NSClassFromString(@“__NSArrayM”)获取到的类对象,那么这个__NSArrayM是什么?
因为NSArray,NSMutableArray在Foundation框架中设计成类簇,我们使用array时实际上是系统“偷偷”帮我们在用它们的子类,通过代码看一下:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSArray *arr0 = [[NSArray alloc] init];
NSArray *arr1 = @[@"1"];
NSArray *arr2 = @[@"1",@"2"];
NSMutableArray *m_arr0 = [[NSMutableArray alloc] initWithArray:arr0];
NSMutableArray *m_arr1 = [[NSMutableArray alloc] initWithArray:arr1];
NSMutableArray *m_arr2 = [[NSMutableArray alloc] initWithArray:arr2];
NSLog(@"arr0: %@",[arr0 class]);
NSLog(@"arr1: %@",[arr1 class]);
NSLog(@"arr2: %@",[arr2 class]);
NSLog(@"m_arr0: %@",[m_arr0 class]);
NSLog(@"m_arr1: %@",[m_arr1 class]);
NSLog(@"m_arr2: %@",[m_arr2 class]);
return YES;
}
//运行后的输出结果
2021-03-16 00:00:18.438239+0800 CrashCatcherDemo[1414:57908] arr0: __NSArray0
2021-03-16 00:00:18.438364+0800 CrashCatcherDemo[1414:57908] arr1: __NSSingleObjectArrayI
2021-03-16 00:00:18.438464+0800 CrashCatcherDemo[1414:57908] arr2: __NSArrayI
2021-03-16 00:00:18.438570+0800 CrashCatcherDemo[1414:57908] m_arr0: __NSArrayM
2021-03-16 00:00:18.438686+0800 CrashCatcherDemo[1414:57908] m_arr1: __NSArrayM
2021-03-16 00:00:18.438783+0800 CrashCatcherDemo[1414:57908] m_arr2: __NSArrayM
由此可见使用NSArray时我们其实在用的是__NSArray0(array中没有元素)、__NSSingleObjectArrayI(array中只有一个元素)、__NSArrayI(array中有多个元素),而NSMutableArray其实是__NSArrayM,我想此时关于为什么类对象由NSClassFromString(@"__NSArrayM")获取的原因已经明了。
让我们开始第三点,方法交换在+load 和 +initialize进行方法交换有什么区别?
首先我们得先了解这两个方法有什么区别,iOS app启动后进入main函数前会完成所有类的+load方法,+initialize会在该类第一次使用的时候被调用,类似于的懒加载,但是需要注意如果子类没有实现自己的+initialize方法会造成父类+initialize方法被多次调用(为什么会被多次调用而+(void)load不会被多次调用可以看这个demo,https://gitee.com/Jack_1993/load_initialize_demo),所以处理前需要判断,还有方法交换要保证只交换了一(单数)次,如果还不放心的话可以使用dispatch_once保证方法交换只被执行一次,代码供大家参考:
+ (void)initialize{
if (self == [NSArray class]) {
//safe already...
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//do the thing you want...
});
}
}
分享结束,祝愿大家(还有我)面试顺利找到自己心仪的工作,如果需要demo请留言。
网友评论