最近在看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. 【LeetCode】338. Counting Bits (2 solutions)

    Counting Bits Given a non negative integer number num. For every numbers i in the range 0 ≤ i ≤ num  ...

  2. javascript 搜索二叉树

    function Tree() { this.root = null; } Tree.prototype = { constructor: Tree, addItem: function(value) ...

  3. 我的常用mixin 之 lines

    /** * 最多显示 $lineCount 行 * lines * * example: * @include lines; * @include lines(3); */ @mixin lines( ...

  4. Sql中的Merge和output

    先看merge, 不用merge时: --更新 update TA Value ) --插入没有的数据 insert into TA ,,Value from TB ) and TypeName=@v ...

  5. <img> to image_tag

    <img src="clean_wave.jpg?" alt="Clean_wave"><%= image_tag("clean_w ...

  6. mysql隔离级别

    MySQL/InnoDB定义的4种隔离级别: Read Uncommited 可以读取未提交记录. Read Committed (RC) 针对当前读,RC隔离级别保证对读取到的记录加锁 (记录锁), ...

  7. SAP ECC PP 配置文档

    SAP ECC 6.0 Configuration Document Production Planning & Control (PP) 1. General Settings 1.1 Ma ...

  8. Git简记

    1. 如何在GitHub上下载资源? 有2种方法: (1)直接在WebBrowser中下载. 比如要下载 https://github.com/numbbbbb/progit-zh-pdf-epub- ...

  9. SQL SERVER 2008 如何查询含有某关键词的表

    最新文章:Virson's Blog 文章来自:百度知道 /** 查询一个数据库中所有的数据中是否包含指定字符串 */ ) set @str='字符串' --要搜索的字符串 ) declare tb ...

  10. [初读笔记] Cloud Migration Research: A Systematic Review (TCC, 2013)

    Pooyan Jamshidi, Aakash Ahmad, Claus Pahl, "Cloud Migration Research: A Systematic Review," ...