Objective-C 利用OC的消息机制,使用Method Swizzling进行方法修改

功能:修改父类不可修改函数方法,函数方法交换

应用场景:假如我们使用的他人提供一个的framework,.m已被打包成二进制.a无法修改源码,只留下.h头文件,那假如代码中某个函数出现了问题可以通过这样的方法进行修改某个函数

一:利用category进行方法覆盖

我们知道,利用category,可以达到“方法覆盖”的效果:

比如:

//
//  Teacher.h

#import <Foundation/Foundation.h>

@interface Teacher : NSObject
- (void) testMethod;
@end

@interface Teacher(LogCategory)

@end
//
//  Teacher.m

#import "Teacher.h"

@implementation Teacher
- (void) testMethod{
    NSLog(@" >> origin testMethod");
}
@end

@implementation Teacher(LogCategory)
- (void) testMethod{
    NSLog(@" >> LogCategory testMethod");
}
@end

通过增加category类调用同样的方法,方法是可以被覆盖的。调用Teacher对象的testMethod输出的是“>> LogCategory testMethod”

二:利用Method Swizzling进行方法修改

如果看过Objective-C的消息机制,应该知道OC的方法实现都是动态的,都是基于Runtime上的消息来实现。

概括如下:

我们来看看具体消息发送之后是怎么来动态查找对应的方法的。

首先,编译器将代码[obj makeText],转化为objc_msgSend(obj, @selector (makeText));

在objc_msgSend函数中。首先通过obj的isa指针找到obj对应的class。在Class中先去cache中 通过SEL查找对应函数method(猜测cache中method列表是以SEL为key通过hash表来存储的,这样能提高函数查找速度)。若 cache中未找到,再去methodList中查找,若methodlist中未找到,则取superClass中查找。若能找到,则将method加 入到cache中,以方便下次查找,并通过method中的函数指针跳转到对应的函数中去执行。

所以可以通过修改 SEL 对应的 Method 的函数指针既可以达到修改消息行为的目的。

代码实现的逻辑是这样:

1.取得 SEL 对应的 Method;
2.修改或交换 Method 的函数指针,在这里是通过系统APImethod_exchangeImplementations()交换实现的。

这里有个需要注意的地方:

"ObjC 中的类(class)和实例(instance)都是对象,类对象有自己的类方法列表,实例对象有自己的实例方法列表,这些方法列表(struct objc_method_list)是存储在 struct objc_class 中的。每个方法列表存储近似 SEL:Method 的对,Method 是一个对象,包含方法的具体实现 impl。"

也就是说,对于类方法和实例方法取得SEL对应的Method函数是不一样的。(比如类方法是Method origMethod = class_getInstanceMethod(self, origSel);)

如下代码就是实现函数交换

.h

#if TARGET_OS_IPHONE
#import <objc/runtime.h>
#import <objc/message.h>
#else
#import <objc/objc-class.h>
#endif

#import <Foundation/Foundation.h>

@interface NSObject (MethodSwizzlingCategory)

+ (BOOL)swizzleMethod:(SEL)origSel withMethod:(SEL)altSel;
+ (BOOL)swizzleClassMethod:(SEL)origSel withClassMethod:(SEL)altSel;

@end

 .m

#import "NSObject+MethodSwizzlingCategory.h"

@implementation NSObject (MethodSwizzlingCategory)

+ (BOOL)swizzleMethod:(SEL)origSel withMethod:(SEL)altSel
{
    Method origMethod = class_getInstanceMethod(self, origSel);
    if (!origSel) {
        NSLog(@"original method %@ not found for class %@", NSStringFromSelector(origSel), [self class]);
        return NO;
    }
    
    Method altMethod = class_getInstanceMethod(self, altSel);
    if (!altMethod) {
        NSLog(@"original method %@ not found for class %@", NSStringFromSelector(altSel), [self class]);
        return NO;
    }
    
    class_addMethod(self,
                    origSel,
                    class_getMethodImplementation(self, origSel),
                    method_getTypeEncoding(origMethod));
    class_addMethod(self,
                    altSel,
                    class_getMethodImplementation(self, altSel),
                    method_getTypeEncoding(altMethod));
    
    method_exchangeImplementations(class_getInstanceMethod(self, origSel), class_getInstanceMethod(self, altSel));
    
    return YES;
}

+ (BOOL)swizzleClassMethod:(SEL)origSel withClassMethod:(SEL)altSel
{
    Class c = object_getClass((id)self);
    return [c swizzleMethod:origSel withMethod:altSel];
}

@end

具体使用如下:

//
//  Teacher.h

#import <Foundation/Foundation.h>
#import "NSObject+MethodSwizzlingCategory.h"

@interface Teacher : NSObject
- (void) originLog;
@end

@interface SuperTeacher:Teacher
- (void) altLog;
@end
//
//  Teacher.m

#import "Teacher.h"

@implementation Teacher

- (void) originLog{
    NSLog(@" >> origin Log");
}
@end

@implementation SuperTeacher
- (void) altLog{
    NSLog(@" >> alt Log");
}
@end

实现代码:

    Teacher *tc=[[Teacher alloc]init];
    SuperTeacher *stc=[[SuperTeacher alloc]init];
    [stc originLog];
    [stc altLog];
    
    NSLog(@"========= Method Swizzling test =========");
    [SuperTeacher swizzleMethod:@selector(originLog) withMethod:@selector(altLog)];
    [tc originLog];
    [stc altLog];

输出:

2015-12-08 11:45:47.559 OCTest[2807:150681]  >> origin Log
2015-12-08 11:45:47.560 OCTest[2807:150681]  >> alt Log
2015-12-08 11:45:47.560 OCTest[2807:150681] ========= Method Swizzling test =========
2015-12-08 11:45:47.561 OCTest[2807:150681]  >> alt Log
2015-12-08 11:45:47.561 OCTest[2807:150681]  >> origin Log

说明[stc altlog]消息被指向了调用originLog函数,[stc originlog]消息指向了altlog函数。

参考:

http://blog.csdn.net/kesalin/article/details/7178871

原文地址:https://www.cnblogs.com/rayshen/p/5028649.html