理解Objective-C Runtime(四)Method Swizzling
Objective-C对象收到消息之后,究竟会调用何种方法需要在运行期间才能解析出来。那你也许会问:与给定的选择子名称相应的方法是不是也可以在runtime改变呢?没错,就是这样。若能善用此特性,则可发挥出巨大优势,因为我们既不需要源代码,也不需要通过继承子类来覆写方法就能改变这个类本身的功能。这样一来,新功能将在本类的所有实例中生效,而不仅限于覆写了相关方法的那些子类实例。此方案就是大名鼎鼎的「method swizzling」,中文常称之为『方法调配』或『方法调和』或『方法混合』。
Method Swizzling
类的方法列表会把选择子的名称映射到相关的方法实现之上,使得「动态消息派发系统」(dynamic message-dispatch system)能够据此找到应该调和的方法。这些方法均以函数指针的形式来表示,这种指针叫IMP(IMP在《理解Objective-C Runtime(一)预备知识》已有说明)。
举个栗子,NSString类可以响应lowercaseString、uppercaseString、capitalizedString等选择子。这张映射表(selector table,也常称为「选择器表」)中的每个选择子都映射到不同的IMP之上,如下图所示:
Objective-C runtime系统提供的几个方法都能够用来操作这张表。开发者可以向其中新增selector,也可以改变某个selector所对应的方法实现,还可以交换两个selector所映射到的指针。经过几次操作之后,类的方法就会变成如下图所示:
在新的映射表中,多了一个名为newSelector的选择子,lowercaseString和uppercaseString的实现则互换了。上述修改均无需编写子类,只要修改方法表的布局即可,就会反映到程序中所有的NSString实例之上。
交换两个方法的实现
现在通过示例代码演绎『调换NSString的lowercaseString和uppercaseString的方法实现』,具体实现操作是这样的:
- (void)viewDidLoad { [super viewDidLoad]; NSString *aString = @"AbcDEfg"; // lowercaseString和uppercaseString交换前:
NSLog(@"lowercaseString和uppercaseString交换前:");
NSLog(@"lowercase of the string : %@", [aString lowercaseString]);
NSLog(@"uppercase of the string : %@", [aString uppercaseString]); // class_getInstanceMethod方法得到Method类型
Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
Method swappedMethod = class_getInstanceMethod([NSString class], @selector(uppercaseString)); // method_exchangeImplementations交换映射指针
method_exchangeImplementations(originalMethod, swappedMethod); // lowercaseString和uppercaseString交换后:
NSLog(@"lowercaseString和uppercaseString交换后:");
NSLog(@"lowercase of the string : %@", [aString lowercaseString]);
NSLog(@"uppercase of the string : %@", [aString uppercaseString]);
} /* 输出结果:
lowercaseString和uppercaseString交换前:
lowercase of the string : abcdefg
uppercase of the string : ABCDEFG
lowercaseString和uppercaseString交换后:
lowercase of the string : ABCDEFG
uppercase of the string : abcdefg
*/
这演示了如何交换两个方法的实现,然而在实际应用中,像这样直接交换两个方法实现,其意义不大,除非闲得蛋疼。但是,可以通过这一手段来为既有的方法实现增添新功能。
修改既有方法的行为
介绍一个技巧,最好的方式就是提出具体的需求,然后用它跟其他的解决方法做比较。
所以,先来看看我们的需求:对 App 的用户行为进行追踪和分析。简单说,就是当用户看到某个View或者点击某个Button的时候,就把这个事件记下来。
手动添加
@implementation MyViewController () - (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated]; // Custom code // Logging
[Logging logWithEventName:@“my view did appear”];
} - (void)myButtonClicked:(id)sender
{
// Custom code // Logging
[Logging logWithEventName:@“my button clicked”];
}
这种方式的缺点也很明显:它破坏了代码的干净整洁。因为 Logging 的代码本身并不属于 ViewController 里的主要逻辑。随着项目扩大、代码量增加,你的 ViewController 里会到处散布着 Logging 的代码。这时,要找到一段事件记录的代码会变得困难,也很容易忘记添加事件记录的代码。
你可能会想到用继承或类别,在重写的方法里添加事件记录的代码。代码可以是长的这个样子:
- (void)myViewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated]; // Custom code // Logging
[Logging logWithEventName:NSStringFromClass([self class])];
} - (void)myButtonClicked:(id)sender
{
// Custom code // Logging
NSString *name = [NSString stringWithFormat:@“my button in %@ is clicked”, NSStringFromClass([self class])];
[Logging logWithEventName:name];
}
Logging 的代码都很相似,通过继承或类别重写相关方法是可以把它从主要逻辑中剥离出来。但同时也带来新的问题:
- 你需要继承UIViewController,UITableViewController,UICollectionViewController所有这些ViewController,或者给他们添加类别;
- 每个ViewController里的ButtonClick方法命名不可能都一样;
- 你不能控制别人如何去实例化你的子类;
- 对于类别,你没办法调用到原来的方法实现,大多时候,我们重写一个方法只是为了添加一些代码,而不是完全取代它;
- 如果有两个类别都实现了相同的方法,运行时没法保证哪一个类别的方法会给调用。
Method Swizzling的做法
Method Swizzling的做法是新增一个方法log_viewDidAppear:
,在这个方法体中调用viewDidAppear:
的方法体;然后将log_viewDidAppear:
和viewDidAppear:
进行调换。呃,有些绕,看图吧:
新增方法log_viewDidAppear:
的实现代码可以这样写:
- (void)log_viewDidAppear:(BOOL)animated
{
[self log_viewDidAppear:animated]; // Logging
[Logging logWithEventName:NSStringFromClass([self class])];
}
看起来,这段代码好像会陷入递归使用的死循环,不过要记住,此方法是准备和viewDidAppear:
方法互换的。所以,在runtime,log_viewDidAppear:
选择子对应的是原来viewDidAppear:
方法的实现;同样,当向对象发送viewDidAppear:消息时,如上这段代码会被调用,而这段代码的第一句是[self log_viewDidAppear:animated];
,这其实是调用原来viewDidAppear:
方法的实现代码…
定义了log_viewDidAppear:
的实现后,还得与viewDidAppear:
进行交换:
// class_getInstanceMethod方法得到Method类型
Method originalMethod = class_getInstanceMethod([NSString class], @selector(viewDidAppear:));
Method swappedMethod = class_getInstanceMethod([NSString class], @selector(log_viewDidAppear:)); // method_exchangeImplementations交换映射指针
method_exchangeImplementations(originalMethod, swappedMethod);
如何安排method swizzling相关的代码?
一般来说,runtime相关的代码都会以category的形式组织,所以上述log_viewDidAppear:
方法的实现会写在一个UIViewController category中,比如UIViewController(log)
。而「交换方法」相关的代码会写在category的load中。因为load方法是在runtime之前就被执行的,只要category所在的头文件被引用,load方法就会被调用,并且同一个class在不同category之间允许有多个load方法,这些load方法都会被调用(唯一的问题是谁先谁后)。
通过method swizzling方案,开发者可以为那些『完全不知道具体实现的』(completely opaque,『完全不透明』)黑盒方法增加日志记录功能,这非常有助于程序调试,然而,此做法只在调试程序时有用。很少有人在调试程序之外的场合用上述『方法调配技术』来永久改变某个类的功能,因为如果使用不慎,它造成的破坏太大了,并且很难Debug。不能仅仅因为Objective-C语言里有这个特性就一定要用它。若是滥用,反而会令代码变得不易读懂且难于维护。
总之,Method Swizzling只一个挺有争议的技术,对此有很多分析的文章,底部的参考资料中有链接。
AOP(Aspect Oriented Programming)
在阅读博客《Method Swizzling和AOP实践》时了解到了一个新概念 — AOP
。简单来说,在Objective-C世界中,AOP就是利用Runtime特性给指定的方法添加自定义代码,Method Swizzling是其中一种实现AOP的方式之一。
参考资料
- 《Effective Objective-C 2.0》;
- 《iOS开发进阶》;
- 《Method Swizzling和AOP实践》;
- 大神Mattt Thompson(AFNetworking作者)的《Method Swizzling》;
- 《Objective-C的hook方案(一): Method Swizzling》;
- What are the Dangers of Method Swizzling in Objective C?;
- 《Objective-C Runtime》
理解Objective-C Runtime(四)Method Swizzling的更多相关文章
- Method Swizzling和AOP(面向切面编程)实践
Method Swizzling和AOP(面向切面编程)实践 参考: http://www.cocoachina.com/ios/20150120/10959.html 上一篇介绍了 Objectiv ...
- Method Swizzling 和 AOP 实践(转)
上一篇介绍了 Objective-C Messaging.利用 Objective-C 的 Runtime 特性,我们可以给语言做扩展,帮助解决项目开发中的一些设计和技术问题.这一篇,我们来探索一些利 ...
- iOS 使用Method Swizzling隐藏Status Bar
在iOS 6中,隐藏Status Bar很的简单. // iOS 6及曾经,隐藏状态栏 [[UIApplication sharedApplication] setStatusBarHidden:YE ...
- runtime 第四部分method swizzling
接上一篇 http://www.cnblogs.com/ddavidXu/p/5924597.html 转载来源http://www.jianshu.com/p/6b905584f536 http:/ ...
- Objective-C Runtime 运行时之四:Method Swizzling
理解Method Swizzling是学习runtime机制的一个很好的机会.在此不多做整理,仅翻译由Mattt Thompson发表于nshipster的Method Swizzling一文. Me ...
- Objective-C Runtime 运行时之四:Method Swizzling(转载)
理解Method Swizzling是学习runtime机制的一个很好的机会.在此不多做整理,仅翻译由Mattt Thompson发表于nshipster的Method Swizzling一文. Me ...
- runtime实践之Method Swizzling
利用 Objective-C 的 Runtime 特性,我们可以给语言做扩展,帮助解决项目开发中的一些设计和技术问题.这一篇,我们来探索一些利用 Objective-C Runtime 的黑色技巧.这 ...
- Objective-C运行时编程 - 方法混写 Method Swizzling
摘要: 本文描述方法混写对实例.类.父类.不存在的方法等情况处理,属于Objective-C(oc)运行时(runtime)编程范围. 编程环境:Xcode 6.1.1, Yosemite,iOS 8 ...
- 【原】iOS动态性(三) Method Swizzling以及AOP编程:在运行时进行代码注入
概述 今天我们主要讨论iOS runtime中的一种黑色技术,称为Method Swizzling.字面上理解Method Swizzling可能比较晦涩难懂,毕竟不是中文,不过你可以理解为“移花接木 ...
随机推荐
- ubuntu下用webbench做网站压力测试
安装依赖 exuberant-ctags sudo apt-get install exuberant-ctags 下载源码并安装 wget http://blog.s135.com/soft/lin ...
- Codeforces Round #321 (Div. 2) E
终于补好了. 题目链接: http://codeforces.com/contest/580/problem/E E. Kefa and Watch time limit per test 1 sec ...
- disruptor 核心链路应用场景
核心链路一般比较复杂并且需要考虑:服务之间相互依赖性.流程能够走完.人员的变动等情况 要考虑:兜底.补偿. 常见解决方案是:1)完全解耦 2)模板模式 其他解决方案:1)有限状态机框架:spring- ...
- spark再总结
1.Spark是什么? UCBerkeley AMPlab所开源的类HadoopMapReduce的通用的并行计算框架. dfsSpark基于mapreduce算法实现的分布式计算,拥有HadoopM ...
- Android Studio一些常用的快捷键
光标移动和窗口切换:1.esc:光标从功能窗口回到编辑窗口 2.alt+num:打开指定的功能窗口,重复操作关闭该窗口. 3.alt+←→:切换编辑的文件. 4.ctrl+home/end:跳转到文件 ...
- flask的run的运行参数含义
直接阅读源代码吧: 在flask的app.py里,查看run函数的定义 def run(self, host=None, port=None, debug=None, **options): &quo ...
- sklearn 特征选择
1.移除低方差的特征(Removing features with low variance) VarianceThreshold 是特征选择中的一项基本方法.它会移除所有方差不满足阈值的特征.默认设 ...
- 自主学习Flappy Bird游戏
背景 强化学习 MDP基本元素 这部分比较难懂,没有详细看:最优函数值,最优控制等 Q-learning 神经网络 环境搭建 windows下通过pip安装TensorFlow,opencv-pyth ...
- python实现的一个简单的网页爬虫
学习了下python,看了一个简单的网页爬虫:http://www.cnblogs.com/fnng/p/3576154.html 自己实现了一个简单的网页爬虫,获取豆瓣的最新电影信息. 爬虫主要是获 ...
- 腾讯云图片鉴黄集成到C# SQL Server 怎么在分页获取数据的同时获取到总记录数 sqlserver 操作数据表语句模板 .NET MVC后台发送post请求 百度api查询多个地址的经纬度的问题 try{}里有一个 return 语句,那么紧跟在这个 try 后的 finally {}里的 code 会 不会被执行,什么时候被执行,在 return 前还是后? js获取某个日期
腾讯云图片鉴黄集成到C# 官方文档:https://cloud.tencent.com/document/product/641/12422 请求官方API及签名的生成代码如下: public c ...