* author:conowen@大鐘
* E-mail:conowen@hotmail.com
Method Swizzling
由這一篇博文可知,OC的方法調(diào)用實(shí)質(zhì)是消息發(fā)送,OC語言具有動(dòng)態(tài)性,在運(yùn)行的時(shí)候才會(huì)尋找方法的實(shí)現(xiàn),因此,可以利用這個(gè)屬性,動(dòng)態(tài)更改方法體。
簡(jiǎn)單來說,Method Swizzling
就是交換兩個(gè)方法體,因?yàn)槊總€(gè)類都維護(hù)一個(gè)方法(Method)列表,Method則包含SEL和其對(duì)應(yīng)IMP的信息,方法交換做的事情就是把SEL和IMP的對(duì)應(yīng)關(guān)系互換。
主要API
//獲取通過SEL獲取一個(gè)方法
class_getInstanceMethod
//獲取一個(gè)方法的實(shí)現(xiàn)
method_getImplementation
//給方法添加實(shí)現(xiàn)
class_addMethod
//用一個(gè)方法的實(shí)現(xiàn)替換另一個(gè)方法的實(shí)現(xiàn)
class_replaceMethod
//交換兩個(gè)方法的實(shí)現(xiàn)
method_exchangeImplementations
數(shù)組越界保護(hù)
在OC編程中,數(shù)組是經(jīng)常使用的對(duì)象,使用過程中經(jīng)常出現(xiàn)數(shù)組越界,或者非空問題,利用Method Swizzling
技術(shù),可以避免這個(gè)問題。
主要流程是新建一個(gè)NSArray+Safe.h
Category文件,在+ (void)load
完成關(guān)鍵的方法交互即可。
//
// 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 {
/** 不可變數(shù)組 */
//空
[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數(shù)組越界");
return nil;
}
id obj = [self nonEmptyObjectAtIndex:index];
if ([obj isKindOfClass:[NSNull class]]) {
NSLog(@"NSArray數(shù)組取出的元素類型為 NSNull");
return nil;
}
return obj;
}
// NSMutableArray 不為空
- (id)mutableObjectAtIndex:(NSInteger)index{
if (index >= self.count || index < 0) {
NSLog(@"NSMutableArray數(shù)組越界");
return nil;
}
id obj = [self mutableObjectAtIndex:index];
if ([obj isKindOfClass:[NSNull class]]) {
NSLog(@"NSMutableArray數(shù)組取出的元素類型為 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的代碼,在類初始加載時(shí)自動(dòng)被調(diào)用,load方法按照父類到子類,類自身到Category的順序被調(diào)用.
在dispatch_once中執(zhí)行Method Swizzling是一種防護(hù)措施,以保證代碼塊只會(huì)被執(zhí)行一次并且線程安全,不過此處并不需要,因?yàn)楫?dāng)前Category中的load方法并不會(huì)被多次調(diào)用.
嘗試先調(diào)用class_addMethod方法,以保證即便originalSelector只在父類中實(shí)現(xiàn),也能達(dá)到Method Swizzling的目的.
遞歸疑問
// NSArray 不為空
- (id)nonEmptyObjectAtIndex:(NSInteger)index{
if (index >= self.count || index < 0) {
NSLog(@"NSArray數(shù)組越界");
return nil;
}
id obj = [self nonEmptyObjectAtIndex:index];
if ([obj isKindOfClass:[NSNull class]]) {
NSLog(@"NSArray數(shù)組取出的元素類型為 NSNull");
return nil;
}
return obj;
}
看到id obj = [self nonEmptyObjectAtIndex:index];
有些人可能疑問,這里不是導(dǎo)致遞歸了嗎?
其實(shí)并不是,Method Swizzling
可以理解為"方法互換"。假設(shè)我們將A和B兩個(gè)方法進(jìn)行互換,向A方法發(fā)送消息時(shí)執(zhí)行的卻是B方法,向B方法發(fā)送消息時(shí)執(zhí)行的是A方法。所以id obj = [self nonEmptyObjectAtIndex:index];
執(zhí)行的其實(shí)是id obj = [self objectAtIndex:index];