【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实现原理:遍历字 ...
随机推荐
- Swift中的富文本注释格式
Apple当前启用了Markup语法形式的富文本注释格式,并且为此列出了官方文档,可以参考此链接:https://developer.apple.com/library/ios/documentati ...
- EF: Returns multi table from procedure
原文:https://msdn.microsoft.com/en-us/data/jj691402.aspx
- 查看JAVA进程中哪个线程CPU消耗最高
一,在centos linux 上查看进程占用cpu过高 top shift+h 查看哪个进程程消耗最高 二,查看JAVA进程中哪个线程消耗最高 2.1 导出java运行的线程信息 ...
- 在 sublime text 3 中添加 Emmet (ZenCoding)
安装 Emmet 插件: 启动 Sublime Text 3,选择 Preferences>Package Control,点选 Package Control:Install Package: ...
- MARIADB 在 OPENSUSE 的安装。
1.MARIADB 在 OPENSUSE 的安装或者升级 (参考 Setting up MariaDB Repositories ) OPENSUSE 从 12.3 版本开始,默认带有 MARI ...
- 关于Linux session管理与GUI架构
google了一下感觉一下子找不到太好的资料,可能需要慢慢深入去学习. 这里有一个讲session management的,还算比较深入: https://dvdhrm.wordpress.com/2 ...
- 随手写的自动批量编译部署NativeAndroid程序Python脚本
背景 有一堆工程NativeAndroid程序,要一一编译部署编译测试,手头只有AndroidManifest和Makefile,需要一个个Update,Ndk-build,和发包安装测试,很是头疼, ...
- Android Studio开发第四篇版本管理Git(下)
前面一片介绍了在as下如何关联远程仓库,这篇就介绍在开发过程中怎么应用. 提交+Push 如果本地开发代码有改动了或者你觉得某功能做完了,你打算把改动代码提交到远程仓库,这个时候很简单, 还是在工具栏 ...
- HDU 3999 The order of a Tree
The order of a Tree Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Othe ...
- EF执行存储过程时超时问题
异常信息:Message = EF "Timeout 时间已到.在操作完成之前超时时间已过或服务器未响应." ((IObjectContextAdapter);