最近在看Object-C运行时特性,其中有一个特别好用的特性叫 Method Swizzling ,可以动态交换函数地址,在应用程序加载的时候,通过运行时特性互换两个函数的地址,不改变原有代码而改变原有行为,达到偷天换日的效果,下面直接看效果吧

1、我们先创建一个Calculator类,并提供两个简单的方法

#import <Foundation/Foundation.h>

@interface Calculator : NSObject

+ (instancetype)shareInstance;

- (NSInteger)addA:(NSInteger)a withB:(NSInteger)b;

- (void)doSomethingWithParam:(NSString *)param
success:(void (^)(NSString *result))success
failure:(void (^)(NSString *error))failure; @end @implementation Calculator + (instancetype)shareInstance
{
static id instance = nil; static dispatch_once_t token;
dispatch_once(&token, ^{
instance = [[self alloc] init];
}); return instance;
} - (NSInteger)addA:(NSInteger)a withB:(NSInteger)b
{
return a + b;
} - (void)doSomethingWithParam:(NSString *)param
success:(void (^)(NSString *result))success
failure:(void (^)(NSString *error))failure
{
//TODO: do some things, //simulating result
BOOL result = arc4random() % == ;
if (result) {
success(@"success");
} else {
failure(@"error");
}
} @end

2、接下来我们在ViewController测试一下

#import "ViewController.h"
#import "Calculator.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad
{
[super viewDidLoad]; Calculator *calculator = [Calculator shareInstance];
NSInteger addResult = [calculator addA: withB:];
NSLog(@"calculate result: %ld", addResult); [calculator doSomethingWithParam:@"param" success:^(NSString *result) {
NSLog(@"doSomething %@", result);
} failure:^(NSString *error) {
NSLog(@"doSomethime %@", error);
}];
} - (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @end

3、两个函数执行后,输出结果如下

4、现在我们有一个需求,在这这两个函数执行的前后在控制台输出执行信息

  在 doSomethingWithParam:success:failure: 执行成功或失败的时候也输出信息,在不修改原有代码的情况下,我们可以根据Runtime的API自定义一个新的函数,然后再执行原函数前后输出信息

  4.1、我们先创建一个工具类 SGRumtimeTool 用于交换函数

#import <Foundation/Foundation.h>
#import <objc/runtime.h> @interface SGRumtimeTool : NSObject + (void)changeMethodWithClass:(Class)class oldMethod:(SEL)oldMethod newMethod:(SEL)newMethod; @end @implementation SGRumtimeTool + (void)changeMethodWithClass:(Class)class oldMethod:(SEL)oldMethod newMethod:(SEL)newMethod
{
Method originalMethod = class_getInstanceMethod(class, oldMethod);
Method swizzledMethod = class_getInstanceMethod(class, newMethod);
BOOL didAddMethod =
class_addMethod(class,
oldMethod,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod)); if (didAddMethod) {
class_replaceMethod(class,
oldMethod,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
} @end

  4.2、通过分类的方式,定义新函数,同时在初始化时互换方法(load)

    注:NSObject 提供了两个静态的初始化方法 initialize 和 load,load在应用程序启动后就会执行,而initialize在类被第一次使用的时候执行,关于 load 和initialize 的区别的详细分析,参见:http://www.cnblogs.com/ider/archive/2012/09/29/objective_c_load_vs_initialize.html

    推荐大家看一下上面的文章

    下面我们定义 Calculator 的扩展分类

#import "Calculator.h"
#import "SGRumtimeTool.h" @interface Calculator (Monitor) @end @implementation Calculator (Monitor) + (void)load
{
SEL oldAddMethod = @selector(addA:withB:);
SEL newAddMethod = @selector(newAddA:withB:);
[SGRumtimeTool changeMethodWithClass:[self class] oldMethod:oldAddMethod newMethod:newAddMethod]; SEL oldSomeMethod = @selector(doSomethingWithParam:success:failure:);
SEL newSomeMethod = @selector(newDoSomethingWithParam:success:failure:);
[SGRumtimeTool changeMethodWithClass:[self class] oldMethod:oldSomeMethod newMethod:newSomeMethod];
} /**
* log some info before and after the method
*/
- (NSInteger)newAddA:(NSInteger)a withB:(NSInteger)b
{
NSLog(@"-------------- executing addA:withB: --------------");
//two method has swapped, call (newAddA:withB) will execute (addA:withB)
NSInteger result = [self newAddA:a withB:b];
NSLog(@"-------------- executed addA:withB: --------------"); return result;
} /**
* log some info for the result
*/
- (void)newDoSomethingWithParam:(NSString *)param
success:(void (^)(NSString *result))success
failure:(void (^)(NSString *error))failure
{
NSLog(@"-------------- executing doSomethingWithParam:success:failure: --------------"); [self newDoSomethingWithParam:param success:^(NSString *result) {
success(result);
NSLog(@"-------------- execute success --------------");
} failure:^(NSString *error) {
failure(error);
NSLog(@"-------------- execute failure --------------");
}];
} @end

    在Calculator (Monitor) 中,我们定义两个新方法,并添加了一些输出信息,当然我们可以根据我们的信息任意的修改该方法,调用的地方不变

    上面方法看起来像递归调用,进入死循环了,但由于新方法与原来的方法进行了互换,所以我们在新函数调用原来的方法的时候需要使用新的方法名,不会死循环

  4.3、调用的地方不变,运行一下看结果

 原来所有的代码都不变,我们只是新增了一个 Calculator (Monitor) 分类而已

5、Demo

  http://files.cnblogs.com/files/bomo/MonitorDemo.zip

6、总结

  通过这个特性,我们可以用到监控和统计上,我们可以在相关的函数进行埋点,统计一个函数调用了多少次,请求成功率,失败日志的统计等,也可以在不改变原来代码的情况下修复一些bug,例如在有些不能直接修改源码的地方

  

个人水平有限,如果本文由不足或者你有更好的想法,欢迎留言讨论

【iOS】利用Runtime特性做监控的更多相关文章

  1. iOS利用Runtime自定义控制器POP手势动画

    前言 苹果在iOS 7以后给导航控制器增加了一个Pop的手势,只要手指在屏幕边缘滑动,当前的控制器的视图就会跟随你的手指移动,当用户松手后,系统会判断手指拖动出来的大小来决定是否要执行控制器的Pop操 ...

  2. ios 利用runtime任性跳转

    在开发项目中,会有这样变态的需求: 推送:根据服务端推送过来的数据规则,跳转到对应的控制器 feeds列表:不同类似的cell,可能跳转不同的控制器(嘘!产品经理是这样要求:我也不确定会跳转哪个界面哦 ...

  3. iOS - 利用runtime加深对基础知识的理解

    利用runtime加深对基础知识的理解 如果对runtime需要学习,可以看这篇,以下仅作为学习笔记,相互交流. runtime的头文件: #import <objc/runtime.h> ...

  4. Objective-C Json转Model(利用Runtime特性)

    封装initWithNSDictionary:方法 该方法接收NSDictionary对象, 返回PersonModel对象. #pragma mark - 使用runtime将JSON转成Model ...

  5. iOS中利用 runtime 一键改变字体

    1.准备 我们新建一个项目名叫ChangeFont,然后我就随便找了个名叫loveway.ttf的字体库拖进去,里面的工程目录大概就是这样的 目录 现在我们就简单的直接在storyboard上拖了一个 ...

  6. UIView封装动画--iOS利用系统提供方法来做转场动画

    UIView封装动画--iOS利用系统提供方法来做转场动画 UIViewAnimationOptions option; if (isNext) { option=UIViewAnimationOpt ...

  7. UIView封装动画--iOS利用系统提供方法来做关键帧动画

    iOS利用系统提供方法来做关键帧动画 ios7以后才有用. /*关键帧动画 options:UIViewKeyframeAnimationOptions类型 */ [UIView animateKey ...

  8. UIView封装动画--iOS 利用系统提供方法来做弹性运动

    iOS 利用系统提供方法来做弹性运动 /*创建弹性动画 damping:阻尼,范围0-1,阻尼越接近于0,弹性效果越明显 velocity:弹性复位的速度 */ [UIView animateWith ...

  9. ios开发runtime学习五:KVC以及KVO,利用runtime实现字典转模型

    一:KVC和KVO的学习 #import "StatusItem.h" /* 1:总结:KVC赋值:1:setValuesForKeysWithDictionary实现原理:遍历字 ...

随机推荐

  1. asp.net 多个文件同时下载

    1.首先读取文件夹下的文件,可能同时存在多个文件 2.选中文件,然后点击下载,同时可以选择多个文件. 思路:通过生产压缩包的形式进行下载,然后再清楚压缩包,这样用户可以一次性全部下载下来. 一.获取目 ...

  2. Windows 2008 利用Filezilla server搭建FTP

    Windows 2008 利用Filezilla server搭建FTP, 安装后总是提示Error Connection To Server Lost , 后来,无意中先安装了IIS,再安装file ...

  3. ecshop 全站内页 显示最新文章

    一.打开根目录下index.php,找到代码 $smarty->assign('new_articles',    index_get_new_articles());   // 最新文章 进行 ...

  4. [转载] Calculating Entropy

    From:  johndcook.com/blog For a set of positive probabilities pi summing to 1, their entropy is defi ...

  5. 聊聊 Linux 中的五种 IO 模型

    本文转载自: http://mp.weixin.qq.com/s?__biz=MzAxODI5ODMwOA==&mid=2666538919&idx=1&sn=6013c451 ...

  6. Android-NDK编译:cocos2d-x(二)

    看了看NDK的文档....里面总是莫名的会提下windows需要cygwin环境... 但是cocos2d-x3.0alpha1 中, 编译samples的python脚本 cocos2d-x3/bu ...

  7. swift 类和结构体

    1:类和结构体定义 类和结构体分别通过关键字class 和struct定义. swift的编码风格是类class和结构体struct名字使用大写字母开头的匈牙利表示法,相反的.类的方法和属性则用小写字 ...

  8. SZ,RZ传送文件

    linux 和window之间通过xshell的命令 SZ,RZ传送文件:

  9. iOS8中使用CoreLocation定位[转]

    本文转自:http://blog.devzeng.com/blog/ios8-corelocation-framework.html iOS8以前使用CoreLocation定位 1.首先定义一个全局 ...

  10. HTML 表格的属性设置

    对于很多初学HTML的人来说,表格<table>是最常用的标签了,但对于表格边框的控制,很多初学者却不甚其解.   对于很多初学HTML的人来说,表格<table>是最常用的标 ...