您的位置:首页 > 移动开发 > Objective-C

Objective-C的hook方案(一): Method Swizzling

2016-01-19 17:06 603 查看
Objective-C的hook方案(一): Method Swizzling

在没有一个类的实现源码的情况下,想改变其中一个方法的实现,除了继承它重写、和借助类别重名方法暴力抢先之外,还有更加灵活的方法吗?在Objective-C编程中,如何实现hook呢?标题有点大,计划分几篇来总结。

本文主要介绍针对selector的hook,主角被标题剧透了———— Method Swizzling 。


Method Swizzling 原理

在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的。

每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向具体的Method实现。



我们可以利用 method_exchangeImplementations 来交换2个方法中的IMP,

我们可以利用 class_replaceMethod 来修改类,
我们可以利用 method_setImplementation 来直接设置某个方法的IMP,

……

归根结底,都是偷换了selector的IMP,如下图所示:




Method Swizzling 实践

举个例子好了,我想钩一下NSArray的lastObject 方法,只需两个步骤。

第一步:给NSArray加一个我自己的lastObject

#import "NSArray+Swizzle.h"

@implementation NSArray (Swizzle)

- (id)myLastObject

{

id ret = [self myLastObject];

NSLog(@"********** myLastObject *********** ");

return ret;

}

@end

乍一看,这不递归了么?别忘记这是我们准备调换IMP的selector,[self myLastObject] 将会执行真的 [self lastObject] 。

第二步:调换IMP

#import <objc/runtime.h>

#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;

}

}

控制台输出Log:

2013-07-18 16:26:12.585 Hook[1740:c07] ********** myLastObject ***********

2013-07-18 16:26:12.589 Hook[1740:c07] TEST RESULT : 3

结果很让人欣喜,是不是忍不住想给UIWebView的loadRequest: 加 TODO 了呢?


Method Swizzling 的封装

之前在github上找到的RNSwizzle,推荐给大家,可以搜一下。

[cpp] view
plain copy

print?

//

// RNSwizzle.m

// MethodSwizzle

#import "RNSwizzle.h"

#import <objc/runtime.h>

@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

Method
Swizzling 危险不危险


针对这个问题,我在stackoverflow上看到了满意的答案,这里翻译一下,总结记录在本文中,以示分享:

使用 Method Swizzling 编程就好比切菜时使用锋利的刀,一些人因为担心切到自己所以害怕锋利的刀具,可是事实上,使用钝刀往往更容易出事,而利刀更为安全。

Method swizzling 可以帮助我们写出更好的,更高效的,易维护的代码。但是如果滥用它,也将会导致难以排查的bug。



背景

好比设计模式,如果我们摸清了一个模式的门道,使用该模式与否我们自己心里有数。单例模式就是一个很好的例子,它饱受争议但是许多人依旧使用它。Method Swizzling也是一样,一旦你真正理解它的优势和弊端,使用它与否你应该就有你自己的观点。


讨论

这里是一些 Method Swizzling的陷阱:

Method swizzling is not atomic
Changes behavior of un-owned code
Possible naming conflicts
Swizzling changes the method's arguments
The order of swizzles matters
Difficult to understand (looks recursive)
Difficult to debug

我将逐一分析这些点,增进对Method Swizzling的理解的同时,并搞懂如何应对。

http://blog.csdn.net/yiyaaixuexi/article/details/9374411
详情请看念茜的博客

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: