在没有一个类的实现源码的情况下,想改变其中一个方法的实现,除了继承它重写、和借助类别重名方法暴力抢先之外,还有更加灵活的方法吗?在Objective-C编程中,如何实现hook呢?标题有点大,计划分几篇来总结。
本文主要介绍针对selector的hook,主角被标题剧透了———— Method Swizzling 。
每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向具体的Method实现。
我们可以利用 method_exchangeImplementations 来交换2个方法中的IMP,
我们可以利用 class_replaceMethod 来修改类,
我们可以利用 method_setImplementation 来直接设置某个方法的IMP,归根结底,都是偷换了selector的IMP,如下图所示:
第一步:给NSArray加一个我自己的lastObject
#import "NSArray+Swizzle.h"
@implementation NSArray (Swizzle)
- (id)myLastObject
{
id ret = [self myLastObject];
NSLog(@"********** myLastObject *********** ");
return ret;
}
@end
#import
#import "NSArray+Swizzle.h"
int main(int argc, char *argv[])
{
@autoreleasepool {
Method ori_Method = class_getInstanceMethod([NSArray class], @selector(lastObject));
Method my_Method = class_getInstanceMethod([NSArray class], @selector(myLastObject));
method_exchangeImplementations(ori_Method, my_Method);
NSArray *array = @[@"0",@"1",@"2",@"3"];
NSString *string = [array lastObject];
NSLog(@"TEST RESULT : %@",string);
return 0;
}
}
2013-07-18 16:26:12.585 Hook[1740:c07] ********** myLastObject ***********
2013-07-18 16:26:12.589 Hook[1740:c07] TEST RESULT : 3
//
// RNSwizzle.m
// MethodSwizzle
#import "RNSwizzle.h"
#import
@implementation NSObject (RNSwizzle)
+ (IMP)swizzleSelector:(SEL)origSelector
withIMP:(IMP)newIMP {
Class class = [self class];
Method origMethod = class_getInstanceMethod(class,
origSelector);
IMP origIMP = method_getImplementation(origMethod);
if(!class_addMethod(self, origSelector, newIMP,
method_getTypeEncoding(origMethod)))
{
method_setImplementation(origMethod, newIMP);
}
return origIMP;
}
@end
@interface NSView : NSObject
- (void)setFrame:(NSRect)frame;
@end
@implementation NSView (MyViewAdditions)
- (void)my_setFrame:(NSRect)frame {
// do custom work
[self my_setFrame:frame];
}
+ (void)load {
[self swizzle:@selector(setFrame:) with:@selector(my_setFrame:)];
}
@end
这段代码运行正确,但是如果my_setFrame: 在别处被定义了会发生什么呢?
这个问题不仅仅存在于swizzling,这里有一个替代的变通方法:
@implementation NSView (MyViewAdditions)
static void MySetFrame(id self, SEL _cmd, NSRect frame);
static void (*SetFrameIMP)(id self, SEL _cmd, NSRect frame);
static void MySetFrame(id self, SEL _cmd, NSRect frame) {
// do custom work
SetFrameIMP(self, _cmd, frame);
}
+ (void)load {
[self swizzle:@selector(setFrame:) with:(IMP)MySetFrame store:(IMP *)&SetFrameIMP];
}
@end
typedef IMP *IMPPointer;
BOOL class_swizzleMethodAndStore(Class class, SEL original, IMP replacement, IMPPointer store) {
IMP imp = NULL;
Method method = class_getInstanceMethod(class, original);
if (method) {
const char *type = method_getTypeEncoding(method);
imp = class_replaceMethod(class, original, replacement, type);
if (!imp) {
imp = method_getImplementation(method);
}
}
if (imp && store) { *store = imp; }
return (imp != NULL);
}
@implementation NSObject (FRRuntimeAdditions)
+ (BOOL)swizzle:(SEL)original with:(IMP)replacement store:(IMPPointer)store {
return class_swizzleMethodAndStore(self, original, replacement, store);
}
@end
我认为这是最大的问题。想正常调用method swizzling 将会是个问题。
[self my_setFrame:frame];
直接调用my_setFrame: , runtime做的是
objc_msgSend(self, @selector(my_setFrame:), frame);
[NSButton swizzle:@selector(setFrame:) with:@selector(my_buttonSetFrame:)];
[NSControl swizzle:@selector(setFrame:) with:@selector(my_controlSetFrame:)];
[NSView swizzle:@selector(setFrame:) with:@selector(my_viewSetFrame:)];
[NSView swizzle:@selector(setFrame:) with:@selector(my_viewSetFrame:)];
[NSControl swizzle:@selector(setFrame:) with:@selector(my_controlSetFrame:)];
[NSButton swizzle:@selector(setFrame:) with:@selector(my_buttonSetFrame:)];
(感谢评论中 qq373127202 的提醒,在此更正一下,十分感谢)
多个有继承关系的类的对象swizzle时,先从父对象开始。 这样才能保证子类方法拿到父类中的被swizzle的实现。在+(void)load中swizzle不会出错,就是因为load类方法会默认从父类开始调用。