前言
- MethodSwizzle顧名思義是方法交換,也就是交換方法IMP實(shí)現(xiàn)。一般能做很多面向切面的事,但是如果使用不當(dāng),就會(huì)踩到不少坑。
- 一般是在 + load 中執(zhí)行方法交換的。因?yàn)閘oad方法加載時(shí)機(jī)較早,基本能確保方法已交換。
- 需要確保交換的方法是本類的方法,而不是父類的。直接交換父類方法,會(huì)影響其它子類。
- 方法交換時(shí)還需要特別注意類簇,確保交換的是正確的類。
- 實(shí)例方法存儲(chǔ)在類對(duì)象中,類方法存儲(chǔ)在元類對(duì)象中。
-
經(jīng)典isa流程圖:
isa流程圖.png
開始玩一下
一、首先簡(jiǎn)單實(shí)現(xiàn)一下方法交換
/**
方法交換
@param origSel 原方法名
@param newSel 新方法名
*/
+(void) methodSwizzleWithOrigSel:(SEL)origSel newSel:(SEL)newSel
{
//類對(duì)象(實(shí)例方法存儲(chǔ)在類對(duì)象中) -- 由于此方法是類方法,所以self是類對(duì)象
Class mClass = [self class];
//方法
Method origMethod = class_getInstanceMethod(mClass, origSel);
Method newMethod = class_getInstanceMethod(mClass, newSel);
//imp
IMP origIMP = method_getImplementation(origMethod);
IMP newIMP = method_getImplementation(newMethod);
method_setImplementation(origMethod, newIMP);
method_setImplementation(newMethod, origIMP);
}
- 創(chuàng)建類Person及其子類Student、Student2
Person:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
//名稱
@property (nonatomic,copy) NSString *name;
//年齡
@property (nonatomic,assign) NSInteger age;
-(NSString *)name;
@end
NS_ASSUME_NONNULL_END
#import "Person.h"
@implementation Person
-(NSString *)name
{
return @"person";
}
@end
Student:
#import "Person.h"
NS_ASSUME_NONNULL_BEGIN
@interface Student : Person
//學(xué)科
@property (nonatomic,copy) NSString *subject;
@end
NS_ASSUME_NONNULL_END
Student2:
#import "Person.h"
NS_ASSUME_NONNULL_BEGIN
@interface Student2 : Person
//別名
@property (nonatomic,copy) NSString *nickName;
@end
NS_ASSUME_NONNULL_END
- 我們來hook get方法(在Student類中hook name方法)
#import "Student.h"
#import "NSObject+MethodSwizzle.h"
@implementation Student
+(void) load
{
[self methodSwizzleWithOrigSel:@selector(name) newSel:@selector(myName)];
}
-(NSString *) myName
{
return @"學(xué)生1";
}
@end
- 在ViewController中測(cè)試
#import "ViewController.h"
#import "Student.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Student *stu = [[Student alloc] init];
NSLog(@"%@",stu.name);
}
@end
輸出結(jié)果:

image.png
- 在ViewController中增加Student2的name輸出
#import "ViewController.h"
#import "Student.h"
#import "Student2.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Student *stu = [[Student alloc] init];
NSLog(@"%@",stu.name);
Student2 *stu2 = [[Student2 alloc] init];
NSLog(@"%@",stu2.name);
}
@end
輸出結(jié)果:

image.png
- 結(jié)論
由于子類Student交換的是父類Person的name方法,所以影響了其它子類調(diào)用父類的name方法,都會(huì)變成調(diào)用Student的myName方法。
二、修改一下方法交換的實(shí)現(xiàn)
- 判斷子類是否有實(shí)現(xiàn)需要交換的方法,沒有實(shí)現(xiàn)則添加
/**
方法交換
@param origSel 原方法名
@param newSel 新方法名
*/
+(void) methodSwizzleWithOrigSel:(SEL)origSel newSel:(SEL)newSel
{
//類對(duì)象(實(shí)例方法存儲(chǔ)在類對(duì)象中) -- 由于此方法是類方法,所以self是類對(duì)象
Class mClass = [self class];
//方法
Method origMethod = class_getInstanceMethod(mClass, origSel);
Method newMethod = class_getInstanceMethod(mClass, newSel);
//imp
IMP origIMP = method_getImplementation(origMethod);
IMP newIMP = method_getImplementation(newMethod);
//方法添加成功代表target中不包含原方法,可能是其父類包含(交換父類方法可能有意想不到的問題)
if(class_addMethod(mClass, origSel, origIMP, method_getTypeEncoding(origMethod))){
//直接替換新添加的方法
class_replaceMethod(mClass, origSel, newIMP, method_getTypeEncoding(newMethod));
}else{
method_setImplementation(origMethod, newIMP);
method_setImplementation(newMethod, origIMP);
}
}
-
再次在ViewController中測(cè)試,其代碼不變,輸出結(jié)果:
image.png 結(jié)論
由此看出,此時(shí)交換的本類的方法,不會(huì)影響其它子類調(diào)用方法。但是還有問題,當(dāng)父類方法只聲明了,沒有實(shí)現(xiàn)的話,而你在交換的方法中又需要調(diào)用原方法的時(shí)候,會(huì)產(chǎn)生死遞歸。
- 上述描述問題重現(xiàn)
在Person類增加say方法但不實(shí)現(xiàn),Student類中交換say方法
@interface Person : NSObject
//名稱
@property (nonatomic,copy) NSString *name;
//年齡
@property (nonatomic,assign) NSInteger age;
-(NSString *)name;
-(void) say;
@end
@implementation Student
+(void) load
{
[self methodSwizzleWithOrigSel:@selector(say) newSel:@selector(mySay)];
}
-(NSString *) myName
{
return @"學(xué)生1";
}
-(void) mySay
{
NSLog(@"%@",@"學(xué)生1說話");
//調(diào)用父類方法
[self mySay];
}
@end
輸出結(jié)果:

image.png
三、再次修改一下方法交換的實(shí)現(xiàn)
- 判斷原方法是否有實(shí)現(xiàn),沒有實(shí)現(xiàn)添加一個(gè)空實(shí)現(xiàn)
/**
方法交換
@param origSel 原方法名
@param newSel 新方法名
*/
+(void) methodSwizzleWithOrigSel:(SEL)origSel newSel:(SEL)newSel
{
//類對(duì)象(實(shí)例方法存儲(chǔ)在類對(duì)象中) -- 由于此方法是類方法,所以self是類對(duì)象
Class mClass = [self class];
//方法
Method origMethod = class_getInstanceMethod(mClass, origSel);
Method newMethod = class_getInstanceMethod(mClass, newSel);
if (!origMethod) {//原方法沒實(shí)現(xiàn)
class_addMethod(mClass, origSel, imp_implementationWithBlock(^(id self, SEL _cmd){}), "v16@0:8");
origMethod = class_getInstanceMethod(mClass, origSel);
}
//imp
IMP origIMP = method_getImplementation(origMethod);
IMP newIMP = method_getImplementation(newMethod);
//方法添加成功代表target中不包含原方法,可能是其父類包含(交換父類方法可能有意想不到的問題)
if(class_addMethod(mClass, origSel, origIMP, method_getTypeEncoding(origMethod))){
//直接替換新添加的方法
class_replaceMethod(mClass, origSel, newIMP, method_getTypeEncoding(newMethod));
}else{
method_setImplementation(origMethod, newIMP);
method_setImplementation(newMethod, origIMP);
}
}
輸出結(jié)果:

image.png
四、交換類方法
- 類方法交換基本和實(shí)例方法交換差不多
- 需要注意的是類方法其實(shí)是元類的實(shí)例方法,class_getClassMethod實(shí)際上內(nèi)部還是調(diào)用class_getInstanceMethod。
/***********************************************************************
* class_getClassMethod. Return the class method for the specified
* class and selector.
**********************************************************************/
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
- 所以只要確保class_getInstanceMethod方法中的第一個(gè)參數(shù)是元類對(duì)象,我們就可以直接調(diào)用class_getInstanceMethod來獲取類方法,從而減少調(diào)用class_getClassMethod時(shí)需要的元類判斷。
/**
類方法交換
@param origSel 原類方法名
@param newSel 新類方法名
*/
+(void) methodSwizzleWithOrigClassSel:(SEL)origSel newClassSel:(SEL)newSel
{
//元類(類方法存儲(chǔ)在元類對(duì)象中)
Class metaClass = object_getClass([self class]);
//方法
Method origMethod = class_getInstanceMethod(metaClass, origSel);
Method newMethod = class_getInstanceMethod(metaClass, newSel);
if (!origMethod) {//原方法沒實(shí)現(xiàn)
class_addMethod(metaClass, origSel, imp_implementationWithBlock(^(id self, SEL _cmd){}), "v16@0:8");
origMethod = class_getInstanceMethod(metaClass, origSel);
}
//imp
IMP origIMP = method_getImplementation(origMethod);
IMP newIMP = method_getImplementation(newMethod);
//方法添加成功代表target中不包含原方法,可能是其父類包含(交換父類方法可能有意想不到的問題)
if(class_addMethod(metaClass, origSel, origIMP, method_getTypeEncoding(origMethod))){
//直接替換新添加的方法
class_replaceMethod(metaClass, origSel, newIMP, method_getTypeEncoding(newMethod));
}else{
method_setImplementation(origMethod, newIMP);
method_setImplementation(newMethod, origIMP);
}
}
五、交換類簇的方法
1、問題
- 創(chuàng)建NSArray的分類,檢測(cè)數(shù)組越界
#import "NSArray+CheckSize.h"
#import "NSObject+MethodSwizzle.h"
@implementation NSArray (CheckSize)
+(void) load
{
[self methodSwizzleWithOrigSel:@selector(objectAtIndex:) newSel:@selector(myObjectAtIndex:)];
}
- (id)myObjectAtIndex:(NSUInteger)index
{
if (index > [self count] - 1) {
NSLog(@"數(shù)組越界了");
return nil;
}
return [self myObjectAtIndex:index];
}
@end
- 在ViewController中測(cè)試
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSArray *array = @[@"1",@"2",@"3"];
for (int i = 0 ; i < 4; i++) {
NSLog(@"%d-->%@",i,[array objectAtIndex:i]);
}
}
@end
輸出結(jié)果:

image.png
-
斷點(diǎn)檢測(cè)是否有調(diào)用方法交換
image.png 結(jié)論
方法交換確實(shí)調(diào)用了,那就是已經(jīng)把NSArray的objectAtIndex方法改成分類中的myObjectAtIndex。但是并不管用,一樣奔潰,原因就是因?yàn)轭惔兀梢詮谋罎⑿畔⒅锌吹綄?shí)際調(diào)用的類是__NSArrayI。
2、解決
- 寫一個(gè)新的方法交換方法,支持設(shè)置OrigTarget
/**
方法交換
@param origTarget 被交換方法的類
@param origSel 原方法名
@param newSel 新方法名
*/
+(void) methodSwizzleWithOrigTarget:(Class)origTarget OrigSel:(SEL)origSel newSel:(SEL)newSel
{
//類對(duì)象(實(shí)例方法存儲(chǔ)在類對(duì)象中)
Class origClass = origTarget;
if ([origTarget isKindOfClass:[origTarget class]]) {//成立則origTarget為實(shí)例對(duì)象
origClass = object_getClass(origTarget);
}
//方法
Method origMethod = class_getInstanceMethod(origClass, origSel);
Method newMethod = class_getInstanceMethod(origClass, newSel);
if (!origMethod) {//原方法沒實(shí)現(xiàn)
class_addMethod(origClass, origSel, imp_implementationWithBlock(^(id self, SEL _cmd){}), "v16@0:8");
origMethod = class_getInstanceMethod(origClass, origSel);
}
//imp
IMP origIMP = method_getImplementation(origMethod);
IMP newIMP = method_getImplementation(newMethod);
//方法添加成功代表target中不包含原方法,可能是其父類包含(交換父類方法可能有意想不到的問題)
if(class_addMethod(origClass, origSel, origIMP, method_getTypeEncoding(origMethod))){
//直接替換新添加的方法
class_replaceMethod(origClass, origSel, newIMP, method_getTypeEncoding(newMethod));
}else{
method_setImplementation(origMethod, newIMP);
method_setImplementation(newMethod, origIMP);
}
}
- 修改NSArray分類中的交換方法
#import "NSArray+CheckSize.h"
#import "NSObject+MethodSwizzle.h"
@implementation NSArray (CheckSize)
+(void) load
{
[self methodSwizzleWithOrigTarget:NSClassFromString(@"__NSArrayI") OrigSel:@selector(objectAtIndex:) newSel:@selector(myObjectAtIndex:)];
}
- (id)myObjectAtIndex:(NSUInteger)index
{
if (index > [self count] - 1) {
NSLog(@"數(shù)組越界了");
return nil;
}
return [self myObjectAtIndex:index];
}
@end
-
再次測(cè)試,輸出結(jié)果
image.png
六、結(jié)論
MethodSwizzle雖然好用,但是一不小心,可能讓你找半天都不知道問題出在哪。特別注意多人開發(fā),同時(shí)使用了方法交換相同的方法,會(huì)有很多意想不到的問題。



