* author:conowen@大钟
* E-mail:conowen@hotmail.com
Method Swizzling
由这一篇博文可知,OC的方法调用实质是消息发送,OC语言具有动态性,在运行的时候才会寻找方法的实现,因此,可以利用这个属性,动态更改方法体。
简单来说,Method Swizzling
就是交换两个方法体,因为每个类都维护一个方法(Method)列表,Method则包含SEL和其对应IMP的信息,方法交换做的事情就是把SEL和IMP的对应关系互换。
主要API
//获取通过SEL获取一个方法
class_getInstanceMethod
//获取一个方法的实现
method_getImplementation
//給方法添加实现
class_addMethod
//用一个方法的实现替换另一个方法的实现
class_replaceMethod
//交换两个方法的实现
method_exchangeImplementations
数组越界保护
在OC编程中,数组是经常使用的对象,使用过程中经常出现数组越界,或者非空问题,利用Method Swizzling
技术,可以避免这个问题。
主要流程是新建一个NSArray+Safe.h
Category文件,在+ (void)load
完成关键的方法交互即可。
//
// NSArray+Safe.m
// TestRuntime
//
// Created by Johnny Zhong on 2017/2/3.
// Copyright © 2017年 Royole. All rights reserved.
//
#import "NSArray+Safe.h"
#import <objc/runtime.h>
@implementation NSArray (Safe)
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
@autoreleasepool {
/** 不可变数组 */
//空
[objc_getClass("__NSArray0") swizzleMethod:@selector(objectAtIndex:) swizzledSelector:@selector(emptyObjectAtIndex:)];
//非空NSArray
[objc_getClass("__NSArrayI") swizzleMethod:@selector(objectAtIndex:) swizzledSelector:@selector(nonEmptyObjectAtIndex:)];
//非空NSMutableArray
[objc_getClass("__NSArrayM") swizzleMethod:@selector(objectAtIndex:) swizzledSelector:@selector(mutableObjectAtIndex:)];
}
});
}
#pragma mark - 不可变
// 空
- (id)emptyObjectAtIndex:(NSInteger)index{
return nil;
}
// NSArray 不为空
- (id)nonEmptyObjectAtIndex:(NSInteger)index{
if (index >= self.count || index < 0) {
NSLog(@"NSArray数组越界");
return nil;
}
id obj = [self nonEmptyObjectAtIndex:index];
if ([obj isKindOfClass:[NSNull class]]) {
NSLog(@"NSArray数组取出的元素类型为 NSNull");
return nil;
}
return obj;
}
// NSMutableArray 不为空
- (id)mutableObjectAtIndex:(NSInteger)index{
if (index >= self.count || index < 0) {
NSLog(@"NSMutableArray数组越界");
return nil;
}
id obj = [self mutableObjectAtIndex:index];
if ([obj isKindOfClass:[NSNull class]]) {
NSLog(@"NSMutableArray数组取出的元素类型为 NSNull");
return nil;
}
return obj;
}
#pragma mark - 方法交换
+ (void)swizzleMethod:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector{
Class class = [self class];
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod = class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
@end
需要注意的是:
通过在Category的+ (void)load方法中添加Method Swizzling的代码,在类初始加载时自动被调用,load方法按照父类到子类,类自身到Category的顺序被调用.
在dispatch_once中执行Method Swizzling是一种防护措施,以保证代码块只会被执行一次并且线程安全,不过此处并不需要,因为当前Category中的load方法并不会被多次调用.
尝试先调用class_addMethod方法,以保证即便originalSelector只在父类中实现,也能达到Method Swizzling的目的.
递归疑问
// NSArray 不为空
- (id)nonEmptyObjectAtIndex:(NSInteger)index{
if (index >= self.count || index < 0) {
NSLog(@"NSArray数组越界");
return nil;
}
id obj = [self nonEmptyObjectAtIndex:index];
if ([obj isKindOfClass:[NSNull class]]) {
NSLog(@"NSArray数组取出的元素类型为 NSNull");
return nil;
}
return obj;
}
看到id obj = [self nonEmptyObjectAtIndex:index];
有些人可能疑问,这里不是导致递归了吗?
其实并不是,Method Swizzling
可以理解为"方法互换"。假设我们将A和B两个方法进行互换,向A方法发送消息时执行的却是B方法,向B方法发送消息时执行的是A方法。所以id obj = [self nonEmptyObjectAtIndex:index];
执行的其实是id obj = [self objectAtIndex:index];