OC学习Runtime之Method swizzling

本文介绍了Objective-C中的Method Swizzling技术,包括其定义、使用场景和注意事项。文章强调在交换方法时应确保签名一致,并应在`+load`方法中执行,以保证代码的统一性。此外,通过`dispatch_once`保证方法交换的原子性,防止多线程环境下的问题。文中还详细解释了选择器、方法和实现的概念,并提醒开发者谨慎使用Method Swizzling,以避免潜在的问题和不可预测的行为。
摘要由CSDN通过智能技术生成

坚持 成长 每日一篇

OC的一些黑魔法除了关联对象(associated objects)还有一个比较争议的方法交换(Method swizzling)

Method swizzling指的是改变一个已存在的选择器对应的实现的过程,它依赖于Objectvie-C中方法的调用能够在运行时进改变——通过改变类的调度表(dispatch table)中选择器到最终函数间的映射关系。

这个黑魔法我们在之前其实已经使用比如签名的消息转发中用到了给类添加未知消息的方法等。
但是使用这个黑魔法我们还是要注意一些事项:
1.当我们交换系统类的已有方法时候,最好在分类里面进行,且用来交换的新方法和被交换的方法签名相同(即返回值,参数,和是否类或对象方法相同)
2.交换动作在load和initialize里面进行,交换的操作以单例模式进行(确保只执行一次)。下面会具体介绍为什么要在这两个方法里面使用

+ (void)load;
+ (void)initialize;

3.新的交换方法必须回调自身一次,比如下面代码中的[self xxx_viewWillAppear:animated];
由于- (void)xxx_viewWillAppear:(BOOL)animated { }是新方法的实现所以这里不会造成死循环,如果用这个方法替换UIViewController的ViewWillAppear:当用系统用[vc ViewWillAppear:]时候,系统进入到下面的实现里,但是此时调用[self xxx_viewWillAppear:animated]; 会跳到ViewWillAppear:的方法入口执行,执行结束后才会回来。这样做是为了保证系统类能正常执行原有的方法。

- (void)xxx_viewWillAppear:(BOOL)animated { 
  [self xxx_viewWillAppear:animated]; 
   NSLog(@"viewWillAppear: %@", self); 
} 

在计算机学科中,指针变换(pointer swizzling)是指将基于名字或位置的引用转变为直接的指针引用。 然而在Objective-C中,这个词的起源并不完全知道,但关于这一借鉴其实也很好理解,method swizzling可以通过选择器来改变它引用的函数指针。

load and initialize方法

load和initialize的定义我们可以在NSObject.h里面找到

+ (void)load;
+ (void)initialize;

Method swizzling应该在+load方法中实现。
+load是在一个类最开始加载时调用,+initialize是在应用中第一次调用该类或它的实例的方式之前调用。这两个方法都是可选的,只有实现了才会被执行。

因为method swizzling会影响全局,所以减少冒险情况就很重要。+load能够保证在类初始化的时候就会被加载,这为改变系统行为提供了一些统一性。但+initialize并不能保证在什么时候被调用——事实上也有可能永远也不会被调用,例如应用程序从未直接的给该类发送消息。

dispatch_once
Swizzling应该在dispatch_once中实现。

还是因为swizzling会改变全局,我们需要在运行时采取所有可用的防范措施。保障原子性就是一个措施,它确保代码即使在多线程环境下也只会被执行一次。GCD中的diapatch_once就提供这些保障,它应该被当做swizzling的标准实践。

选择器、方法及实现
在Objective-C中,尽管这些词经常被放在一起来描述消息传递的过程,但选择器、方法及实现分别代表运行时的不同方面。

下面是苹果Objective-C Runtime Reference文档中对它们的描述:
1.选择器(typedef struct objc_selector *SEL):选择器用于表示一个方法在运行时的名字,一个方法的选择器是一个注册到(或映射到)Objective-C运行时中的C字符串,它是由编译器生成并在类加载的时候被运行时系统自动映射。

2.方法(typedef struct objc_method *Method):一个代表类定义中一个方法的不明类型。

3.实现(typedef id (*IMP)(id, SEL, …)):这种数据类型是实现某个方法的函数开始位置的指针,函数使用的是基于当前CPU架构的标准C调用规约。第一个参数是指向self的指针(也就是该类的某个实例的内存空间,或者对于类方法来说,是指向元类(metaclass)的指针)。第二个参数是方法的选择器,后面跟的都是参数。

理解这些概念之间关系最好的方式是:一个类(Class)维护一张调度表(dispatch table)用于解析运行时发送的消息;调度表中的每个实体(entry)都是一个方法(Method),其中key值是一个唯一的名字——选择器(SEL),它对应到一个实现(IMP)——实际上就是指向标准C函数的指针。

Method Swizzling就是改变类的调度表让消息解析时从一个选择器对应到另外一个的实现,同时将原始的方法实现混淆到一个新的选择器。

调用_cmd
下面这段代码看起来像是会导致一个死循环:
- (void)xxx_viewWillAppear:(BOOL)animated {
[self xxx_viewWillAppear:animated];
NSLog(@”viewWillAppear: %@”, NSStringFromClass([self class]));
}

但其实并没有,在Swizzling的过程中,xxx_viewWillAppear:会被重新分配给UIViewController的-viewWillAppear:的原始实现。一个优秀程序员应有的直觉会告诉你在一个方法的实现中通过self调用当前方法自身会产生错误,但是在当前这种情况下,如果我们记住到底是怎么回事更有意义。反而,如果我们在这个方法中调用viewWillAppear:才会真的导致死循环,因为这个方法的实现会在运行时被swizzle到viewWillAppear:的选择器。

记住给swizzled方法加上前缀,这和你需要给可能产生冲突的分类方法加前缀是一个道理。

注意事项
Swizzling被普遍认为是一种巫术,容易导致不可预料的行为和结果。尽管不是最安全的,但是如果你采取下面这些措施,method swizzling还是很安全的。

1.始终调用方法的原始实现(除非你有足够的理由不这么做): API为输入和输出提供规约,但它里面具体的实现其实是个黑匣子,在Method Swizzling过程中不调用它原始的实现可能会破坏一些私有状态,甚至是程序的其他部分。

2.避免冲突:给分类方法加前缀,一定要确保不要让你代码库中其他代码(或是依赖库)在做与你相同的事。

3.理解:只是简单的复制粘贴swizzling代码而不去理解它是怎么运行的,这不仅非常危险,而且还浪费了学习Objective-C运行时的机会。阅读 Objective-C Runtime Reference 和 去理解代码是怎样和为什么这样执行的,努力的用你的理解来消灭你的疑惑。

谨慎行事:不管你多么自信你能够swizzling Foundation、UIKit 或者其他内置框架,请记住所有这些都可能在下一个版本中就不好使。提前做好准备,防范于未然才不至于到时候焦头烂额。

不敢放心大胆的直接使用Objective-C运行时?Jonathan ‘Wolf’ Rentzsch提供了经过实战检验的、支持CocoaPads的库JRSwizzle,它会为你考虑好了一切。

与associated objects一样,method swizzling是一个强大的技术,但是你也应该谨慎使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
TableView 无数据 runtime method swizzling 是一种常用的技术手段,用于在 TableView 中没有数据时,自动地替换原有的方法实现来展示自定义的占位图或提示信息。 在 iOS 开发中,当 TableView 没有数据时,通常会显示一张空白的背景或者一些提示文字,告诉用户当前没有任何数据。而使用 runtime method swizzling 技术,我们可以在 TableView 的相关方法中插入自定义的代码,从而实现自动切换显示空白背景或者提示信息。 具体的实现步骤如下: 1. 创建一个自定义的占位图或提示信息视图,以便在没有数据时显示在 TableView 上。 2. 通过 runtime method swizzling 技术,将 TableView 的 reloadData 方法替换为我们自定义的方法实现。 3. 在自定义的方法实现中,判断 TableView 数据源的数量,如果为零,则将自定义的占位图或提示信息视图添加到 TableView 上,并将 TableView 的背景设置为透明。 4. 如果数据源数量不为零,则将 TableView 的背景设置为默认的 TableView 背景,并调用原有的 reloadData 方法来刷新 TableView。 使用 runtime method swizzling 技术来实现 TableView 无数据时的自定义占位图或提示信息的展示可以提高开发效率,减少了代码的重复编写。同时,由于是替换方法的实现,所以不会对原有的代码产生太多影响,维护成本也较小。但是需要注意的是,使用 runtime method swizzling 技术需要谨慎,遵循苹果官方的 API 规范,以免引发一些潜在的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值