【iOS】利用Runtime特性做监控
最近在看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特性做监控的更多相关文章
- iOS利用Runtime自定义控制器POP手势动画
前言 苹果在iOS 7以后给导航控制器增加了一个Pop的手势,只要手指在屏幕边缘滑动,当前的控制器的视图就会跟随你的手指移动,当用户松手后,系统会判断手指拖动出来的大小来决定是否要执行控制器的Pop操 ...
- ios 利用runtime任性跳转
在开发项目中,会有这样变态的需求: 推送:根据服务端推送过来的数据规则,跳转到对应的控制器 feeds列表:不同类似的cell,可能跳转不同的控制器(嘘!产品经理是这样要求:我也不确定会跳转哪个界面哦 ...
- iOS - 利用runtime加深对基础知识的理解
利用runtime加深对基础知识的理解 如果对runtime需要学习,可以看这篇,以下仅作为学习笔记,相互交流. runtime的头文件: #import <objc/runtime.h> ...
- Objective-C Json转Model(利用Runtime特性)
封装initWithNSDictionary:方法 该方法接收NSDictionary对象, 返回PersonModel对象. #pragma mark - 使用runtime将JSON转成Model ...
- iOS中利用 runtime 一键改变字体
1.准备 我们新建一个项目名叫ChangeFont,然后我就随便找了个名叫loveway.ttf的字体库拖进去,里面的工程目录大概就是这样的 目录 现在我们就简单的直接在storyboard上拖了一个 ...
- UIView封装动画--iOS利用系统提供方法来做转场动画
UIView封装动画--iOS利用系统提供方法来做转场动画 UIViewAnimationOptions option; if (isNext) { option=UIViewAnimationOpt ...
- UIView封装动画--iOS利用系统提供方法来做关键帧动画
iOS利用系统提供方法来做关键帧动画 ios7以后才有用. /*关键帧动画 options:UIViewKeyframeAnimationOptions类型 */ [UIView animateKey ...
- UIView封装动画--iOS 利用系统提供方法来做弹性运动
iOS 利用系统提供方法来做弹性运动 /*创建弹性动画 damping:阻尼,范围0-1,阻尼越接近于0,弹性效果越明显 velocity:弹性复位的速度 */ [UIView animateWith ...
- ios开发runtime学习五:KVC以及KVO,利用runtime实现字典转模型
一:KVC和KVO的学习 #import "StatusItem.h" /* 1:总结:KVC赋值:1:setValuesForKeysWithDictionary实现原理:遍历字 ...
随机推荐
- fastreport totalpage 只有设置doublepassreport为true 才正确否则为0
fastreport totalpage 只有设置doublepassreport为true 才正确否则为0
- 苹果 Mac OS 下查看系统隐藏文件
Mac OS X中有很多系统隐藏的信息文件, 一般在Finder中都是看不到,也修改不了的. 但通过在"终端"中输入命令, 就可以在Finder中显示出来: defaults wr ...
- ★android开发--ListView+Json+异步网络图片加载+滚动翻页的例子(图片能缓存,图片不错乱)
例子中用于解析Json的Gson请自己Google下载 主Activity: package COM.Example.Main; import java.util.HashMap; import ja ...
- sudo 使用不了, the permissions on the /etc/sudoers file are changed to something other than 0440
sudo 使用不了,报错: the permissions on the /etc/sudoers file are changed to something other than 0440 how ...
- Reveal-Plugin-for-Xcode 自动结合 Reveal 进行 UI 分析
下载地址:https://github.com/shjborage/Reveal-Plugin-for-Xcode 还记得之前我们如何使用 Reveal UI 分析工具进行实时查看 UI 的结构吗?如 ...
- JavaScript ---属性
获取属性 可以通过点(.)或方挂号([])运算符来获取属性的值. 对于点(.)来说,右侧必须是一个以属性名称命名的简单标识符.对于[]来说,方挂号内必须是一个计算结果为字符串的表达式. var aut ...
- Debian 基本设置
在此做一下记录,省得以后再老是去搜索了 我的debian版本v:8.1.0 我是在Hyper-V中装的,启动后一直ping不通网址,后来才知道Debian不能直接ping域名,只能ping IP... ...
- D3D11中的MSAA
这两年我的工作都转到了D3D11,目前新出硬件几乎全部支持此标准,加上D3D11接口清晰,概念直观,等到windows7普及,想必未来都是D3D11的天下.最近时间较空,我陆续开始写些基础文章,希望对 ...
- Android应用中使用及实现系统“分享”接口
为了应用的推广.传播,很多的应用中都有“分享”功能,一个按钮,点击后会出现短信.微博等等一切实现了分享功能的应用列表.这一篇文章主要介绍怎么调用分享功能和怎么实现分享接口让自己应用出现分享列表中.An ...
- Oracle DBA的神器: PRM恢复工具,可脱离Oracle软件运行,直接读取Oracle数据文件中的数据
Oracle DBA的神器: PRM恢复工具,可脱离Oracle软件运行,直接读取Oracle数据文件中的数据 PRM 全称为ParnassusData Recovery Manager ,由 诗檀软 ...