runtime在我们日常开发和面试过程中是经常会被提及的一个字眼,也是面试必问内容之一,那么,请让我们跟随apple官方文档来看一看runtime的官方描述是什么。
首先我们进入苹果开发者中心:https://developer.apple.com/documentation/
The Objective-C language defers as many decisions as it can from compile time and link time to runtime. Whenever possible, it does things dynamically. This means that the language requires not just a compiler, but also a runtime system to execute the compiled code. The runtime system acts as a kind of operating system for the Objective-C language; it’s what makes the language work.
苹果官方对runtime的描述:runtime 充当着类似于操作系统的角色,OC之所以能作为动态语言,就是因为runtime的存在。
method-swizzling用法
runtime简单应用
来,先看一段代码;
- (void)viewDidLoad {
[super viewDidLoad];
self.dataArray = @[@"Kevin",@"Tony",@"Tom",@"Bob"];
NSLog(@"%@",self.dataArray[4]);
}
以上代码在编译时可以正常通过,但是运行时会发生错误,因为数组发生了越界,平时日常工作编码我们也会经常遇到这样的问题,
然而,这个问题,我们可以通过runtime机制来解决;
我们先来看一看方法交换的原理:
Method Swizzing是发生在运行时的,主要用于在运行时将两个Method进行交换,我们可以将Method Swizzling代码写到任何地方,但是只有在这段Method Swilzzling代码执行完毕之后互换才起作用。
首先我们需要先定义一个交换方法
/**
交换方法
@param cls 交换对象
@param oriSEL 原始方法编号
@param swizzledSEL 交换的方法编号
*/
+ (void)wzy_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
if (!cls) NSLog(@"传入的交换类不能为空");
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
method_exchangeImplementations(oriMethod, swiMethod);
}
接下来我们对系统的NSArray写一个分类
我们在load方法中对方法进行交换,先声明一个新方法用来替代;
- (id)wzy_objectAtIndex:(NSUInteger)index{
if (index > self.count-1) {
NSLog(@"取值越界了,请记录 : %lu > %lu",(unsigned long)index,self.count-1);
return nil;
}
return [self wzy_objectAtIndex:index];
}
- (id)wzy_objectAtIndexedSubscript:(NSUInteger)index{
if (index > self.count-1) {
NSLog(@"取值越界了,请记录 : %lu > %lu",(unsigned long)index,self.count-1);
return nil;
}
return [self wzy_objectAtIndexedSubscript:index];
}
然后在load中调用交换方法:
[WZYRuntimeTool wzy_methodSwizzlingWithClass:self.class oriSEL:@selector(objectAtIndex:) swizzledSEL:@selector(wzy_objectAtIndex:)];
编译后交换方法并没有生效,通过carsh日志我们可以看出:
2019-02-26 13:32:50.708003+0800 005---Runtime应用[12893:6753643] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndexedSubscript:]: index 4 beyond bounds [0 .. 3]'
__NSArrayI 才是使用该方法的class;
在iOS中NSNumber、NSArray、NSDictionary等这些类都是类簇(Class Clusters),一个NSArray的实现可能由多个类组成。
所以如果想对NSArray进行Swizzling,必须获取到其“真身”进行Swizzling,直接对NSArray进行操作是无效的。
[WZYRuntimeTool wzy_methodSwizzlingWithClass:objc_getClass("__NSArrayI") oriSEL:@selector(objectAtIndex:) swizzledSEL:@selector(wzy_objectAtIndex:)];
[WZYRuntimeTool wzy_methodSwizzlingWithClass:objc_getClass("__NSArrayI") oriSEL:@selector(objectAtIndexedSubscript:) swizzledSEL:@selector(wzy_objectAtIndexedSubscript:)];
然而此刻我们需要思考,直接写在load里面这样是不是安全,如果在其他地方调用了这个类的load方法,会发生什么?
self.dataArray = @[@"Kevin",@"Tony",@"Tom",@"Bob"];
[NSArray load];
NSLog(@"%@",self.dataArray[4]);
通过测试,发现方法交换无效了,经过我们的分析,只要调用load就会交换一次方法,为了避免这个问题,我们需要对方法交换使用单例来完成;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[WZYRuntimeTool wzy_methodSwizzlingWithClass:objc_getClass("__NSArrayI") oriSEL:@selector(objectAtIndex:) swizzledSEL:@selector(wzy_objectAtIndex:)];
[WZYRuntimeTool wzy_methodSwizzlingWithClass:objc_getClass("__NSArrayI") oriSEL:@selector(objectAtIndexedSubscript:) swizzledSEL:@selector(wzy_objectAtIndexedSubscript:)];
});
在使用method-swizzing方法的时候,需要注意几个问题:
1.Swzzing应该在+load方法中执行;
2.Swzzing应该总是在dispatch_once中执行;
3.Swzzing在+load中执行时,不要使用[super load]。如果多次使用则会发生反复交换,交换方法有可能无效;
4.类簇(NS),不能直接用类名去调用Swzzing;
这一篇使用总结暂时到这里,下一篇讲继续整理我在使用swizzing中遇到的问题;