【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实现原理:遍历字 ...
随机推荐
- android 虚线
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http: ...
- EF 5.0 帮助类
EF 5.0 帮助类 加入命名空间: using System; using System.Data; using System.Data.Entity; using System.Data.Enti ...
- 使用Fragment应用放置后台很久,被系统回收,出现crash
使用Fragment应用放置后台很久,被系统回收,出现crash:原因:系统做了源码FragmentActivity调用onSaveInstanceState保存Fragment对象,这时候系统恢复保 ...
- winform C#获得Mac地址,IP地址,子网掩码,默认网关
1.添加程序集 2.引入命名空间 using System.Management; 3.方法 ManagementClass mc = new ManagementClass("Win32_ ...
- 使用 Windows10 自定义交互消息通知
消息通知是最常用的应用功能之一了,但是由于平台的差异,IOS Android 以及 Windows 都有其特殊性,Android开发者在国内常常都是使用三方的一些推送服务,或者是使用自建的服务器为应用 ...
- Java NIO原理分析
Java IO 在Client/Server模型中,Server往往需要同时处理大量来自Client的访问请求,因此Server端需采用支持高并发访问的架构.一种简单而又直接的解决方案是“one-th ...
- Careercup 论坛上较有意思的题目整理
# 数据结构类 ### 线段树 segment tree http://www.careercup.com/question?id=5165570324430848 找区间内的value的个数 二维线 ...
- Golang控制goroutine的启动与关闭
最近在用golang做项目的时候,使用到了goroutine.在golang中启动协程非常方便,只需要加一个go关键字: go myfunc(){ //do something }() 但是对于一些长 ...
- Android Matirx的简介
在Android中,对图片的处理需要使用到Matrix类,Matrix是一个3 x 3的矩阵,他对图片的处理分为四个基本类型: 1.Translate————平移X,Y轴变换,而不是移动图形 2.Sc ...
- ORA-12170:TNS:连接超时
本文转自 http://www.cnblogs.com/kerrycode/archive/2012/12/14/2818421.html 1:首先检查网络是否能ping通 2:检查TNS配置(TNS ...