runtime-基本概念和以及method-swizzling坑遇到的坑(1)

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中遇到的问题;

你可能感兴趣的:(runtime-基本概念和以及method-swizzling坑遇到的坑(1))