运行时runtime深度解析(三)—— Method Swizzling在数组越界上的应用

版本记录

版本号 时间
V1.0 2017.07.27

前言

OC是运行时的语言,底层就是运行时,可以说runtime是OC的底层,很多事情也都可以用运行时解决,下面就讲述一下运行时runtime的知识以及它的妙用。感兴趣的可以看上面几篇。
1. 运行时runtime深度解析(一)—— API
2. 运行时runtime深度解析(二)—— Method Swizzling在页面统计上的应用

数组越界

前面一篇文章讲的是方法互换在页面统计上的应用,本篇文章主要介绍下一点,那就是运行时Method Swizzling在数组越界上的应用。数组越界是最常见和经典的错误,苹果在这方面没有警告,凡是数组越界直接就crash,下面我们就用运行时写一个数组越界的警告。

我们对NSArrayNSMutableArrayNSDictionaryNSMutableDictionary等类进行Method Swizzling,实现方式还是按照上面的例子来做。但是会发现Method Swizzling根本就不起作用,这是因为Method SwizzlingNSArray这些的类簇是不起作用的。因为这些类簇类,其实是一种抽象工厂的设计模式。抽象工厂内部有很多其它继承自当前类的子类,抽象工厂类会根据不同情况,创建不同的抽象对象来进行使用。例如我们调用NSArray的objectAtIndex:方法,这个类会在方法内部判断,内部创建不同抽象类进行操作。

所以也就是我们对NSArray类进行操作其实只是对父类进行了操作,在NSArray内部会创建其他子类来执行操作,真正执行操作的并不是NSArray自身,所以我们应该对其“真身”进行操作。


代码实现

下面就直接看代码,看一下Method Swizzling在数组越界上的应用。

1. NSArray+JJArrayCategory.h
#import 

@interface NSArray (JJArrayCategory)

@end
2.NSArray+JJArrayCategory.m
#import "NSArray+JJArrayCategory.h"
#import 

@implementation NSArray (JJArrayCategory)

#pragma mark - Override Base Function

+ (void)load
{
    [super load];
    
    Method fromMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:));
    Method toMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(JJ_objectAtIndex:));
    method_exchangeImplementations(fromMethod, toMethod);
}

#pragma mark - Action && Notification

- (id)JJ_objectAtIndex:(NSInteger)index
{
    if (self.count - 1 < index) {
        @try {
            return [self JJ_objectAtIndex:index];
        }
        @catch (NSException *exception) {
            NSLog(@"---------- %s Crash Because Method %s  ----------\n", class_getName(self.class), __func__);
            NSLog(@"%@", [exception callStackSymbols]);
            return nil;
        }
        @finally {
            NSLog(@"nothing to do");
        }
    }
    else {
        return [self JJ_objectAtIndex:index];
    }

}

@end
3. JJRuntimeVC.h
#import 

@interface JJRuntimeVC : UIViewController

@end
4.JJRuntimeVC.m
#import "JJRuntimeVC.h"

@interface JJRuntimeVC ()

@end

@implementation JJRuntimeVC

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor greenColor];
    
    NSArray *arr = @[@(1), @(2), @(3), @(4)];
    
    NSInteger value = [[arr objectAtIndex:4] integerValue];
}

@end

下面看输出结果

2017-07-27 19:48:57.121641+0800 JJOC[5768:1916584] ---------- __NSArrayI Crash Because Method -[NSArray(JJArrayCategory) JJ_objectAtIndex:]  ----------
2017-07-27 19:48:57.132802+0800 JJOC[5768:1916584] (
    0   CoreFoundation                      0x0000000183d86ff0  + 148
    1   libobjc.A.dylib                     0x00000001827e8538 objc_exception_throw + 56
    2   CoreFoundation                      0x0000000183c652c8 CFRunLoopRemoveTimer + 0
    3   JJOC                                0x000000010005bdb4 -[NSArray(JJArrayCategory) JJ_objectAtIndex:] + 88
    4   JJOC                                0x000000010005acf8 -[JJRuntimeVC viewDidLoad] + 556
    5   UIKit                               0x0000000189eb5f9c  + 1036
    6   UIKit                               0x0000000189f6e0c4  + 72
    7   UIKit                               0x0000000189f6df9c  + 416
    8   UIKit                               0x0000000189f6d2cc  + 144
    9   UIKit                               0x0000000189f6cd00  + 856
    10  UIKit                               0x0000000189f6c8b4  + 64
    11  UIKit                               0x0000000189f6c818  + 188
    12  UIKit                               0x0000000189eb3158  + 1200
    13  QuartzCore                          0x00000001870a3274  + 148
    14  QuartzCore                          0x0000000187097de8  + 292
    15  QuartzCore                          0x0000000187097ca8  + 32
    16  QuartzCore                          0x0000000187013360  + 252
    17  QuartzCore                          0x000000018703a3c0  + 504
    18  QuartzCore                          0x000000018703ae8c  + 120
    19  CoreFoundation                      0x0000000183d349a0  + 32
    20  CoreFoundation                      0x0000000183d32628  + 372
    21  CoreFoundation                      0x0000000183c62db4 CFRunLoopRunSpecific + 456
    22  UIKit                               0x0000000189f2045c  + 652
    23  UIKit                               0x0000000189f1b130 UIApplicationMain + 208
    24  JJOC                                0x000000010005c050 main + 124
    25  libdyld.dylib                       0x0000000182c7159c  + 4
)
2017-07-27 19:49:10.451961+0800 JJOC[5768:1916584] nothing to do

可见,由于数组越界,正常的抛出了我们定义的异常,以及系统给的崩溃日志。这里大家要注意NSArray的真身是__NSArray,对于这种工厂模式,其实其他类型的也都有真身,包括我们熟知的NSStringNSDIctionary等等。

下面我们打印一下几个有代表性的类的真身。

真身
NSArray __NSArrayI
NSMutableArray __NSArrayM
NSDictionary __NSDictionaryI
NSMutableDictionary __NSDictionaryM

最后介绍一个很有用的框架。
jrswizzle - Method Swizzling封装


参考文献

1. Method Swizzling
2. Objective-C Runtime 运行时之四:Method Swizzling
3. iOS黑魔法-Method Swizzling

后记

未完,待续~~~

运行时runtime深度解析(三)—— Method Swizzling在数组越界上的应用_第1张图片
范爷很美

你可能感兴趣的:(运行时runtime深度解析(三)—— Method Swizzling在数组越界上的应用)