Method Swizzling

[Cocoa]深入浅出Cocoa之 Method Swizzling

cocoaclassinterfacestructmethodsapi

[Cocoa]深入浅出Cocoa之 Method Swizzling

罗朝辉(http://blog.csdn.net/kesalin)

CC许可,转载请注明出处

在前文深入浅出Cocoa之消息中,我简要介绍了ObjC 中消息的基本情况,包括SEL查找,缓存以及消息转发等。在本文中,我要介绍一个很有趣的技术,Method swizzling,通过这个手法,我们可以动态修改方法的实现,从而达到修改类行为的目的。当然,还有其他办法(如 ClassPosing,Category)也可以达到这个目的。ClassPosing 是针对类级别的,是重量级的手法,Category 也差不多,比较重量级,此外 Category 还无法避免下面的递归死循环(如果你的代码出现了如下形式的递归调用,应该考虑一下你的设计,而不是使用在这里介绍的 Method Swizzling 手法,:))。

[cpp] view plaincopyprint?

  1. // Bar
  2. //
  3. @implementation Bar
  4. - (void) testMethod
  5. {
  6. NSLog(@" >> Bar testMethod");
  7. }
  8. @end
  9. // Bar(BarCategory)
  10. //
  11. @implementation Bar(BarCategory)
  12. - (void) altRecursionMethod
  13. {
  14. NSLog(@" >> Bar(BarCategory) recursionMethod");
  15. [self altRecursionMethod];
  16. }
  17. @end

在前文深入浅出Cocoa之消息中提到,ObjC 中的类(class)和实例(instance)都是对象,类对象有自己的类方法列表,实例对象有自己的实例方法列表,这些方法列表(struct objc_method_list)是存储在 struct objc_class 中的。每个方法列表存储近似 SEL:Method 的对,Method 是一个对象,包含方法的具体实现 impl。由此可知,我们只需要修改 SEL 对应的 Method 的 impl 既可以达到修改消息行为的目的。下面来看代码:

[cpp] view plaincopyprint?

  1. void PerformSwizzle(Class aClass, SEL orig_sel, SEL alt_sel, BOOL forInstance)
  2. {
  3. // First, make sure the class isn‘t nil
  4. if (aClass != nil) {
  5. Method orig_method = nil, alt_method = nil;
  6. // Next, look for the methods
  7. if (forInstance) {
  8. orig_method = class_getInstanceMethod(aClass, orig_sel);
  9. alt_method = class_getInstanceMethod(aClass, alt_sel);
  10. } else {
  11. orig_method = class_getClassMethod(aClass, orig_sel);
  12. alt_method = class_getClassMethod(aClass, alt_sel);
  13. }
  14. // If both are found, swizzle them
  15. if ((orig_method != nil) && (alt_method != nil)) {
  16. IMP temp;
  17. temp = orig_method->method_imp;
  18. orig_method->method_imp = alt_method->method_imp;
  19. alt_method->method_imp = temp;
  20. } else {
  21. #if DEBUG
  22. NSLog(@"PerformSwizzle Error: Original %@, Alternate %@",(orig_method == nil)[email protected]" not found":@" found",(alt_method == nil)[email protected]" not found":@" found");
  23. #endif
  24. }
  25. } else {
  26. #if DEBUG
  27. NSLog(@"PerformSwizzle Error: Class not found");
  28. #endif
  29. }
  30. }
  31. void MethodSwizzle(Class aClass, SEL orig_sel, SEL alt_sel)
  32. {
  33. PerformSwizzle(aClass, orig_sel, alt_sel, YES);
  34. }
  35. void ClassMethodSwizzle(Class aClass, SEL orig_sel, SEL alt_sel)
  36. {
  37. PerformSwizzle(aClass, orig_sel, alt_sel, NO);
  38. }

让我们来分析上面代码:
1,首先,区分类方法和实例方法;
2,取得 SEL 对应的 Method;
3,修改 Method 的 impl,在这里是通过交换实现的。

上面的代码是可以工作的,但还不够完善。Apple 10.5 提供了交换 Method 实现的 API: method_exchangeImplementations 。下面我们使用这个新 API,并以 NSObject category的形式给出新的实现方式:

[cpp] view plaincopyprint?

  1. #if TARGET_OS_IPHONE
  2. #import <objc/runtime.h>
  3. #import <objc/message.h>
  4. #else
  5. #import <objc/objc-class.h>
  6. #endif
  7. // NSObject (MethodSwizzlingCategory)
  8. //
  9. @interface NSObject (MethodSwizzlingCategory)
  10. + (BOOL)swizzleMethod:(SEL)origSel withMethod:(SEL)altSel;
  11. + (BOOL)swizzleClassMethod:(SEL)origSel withClassMethod:(SEL)altSel;
  12. @end
  13. @implementation NSObject (MethodSwizzlingCategory)
  14. + (BOOL)swizzleMethod:(SEL)origSel withMethod:(SEL)altSel
  15. {
  16. Method origMethod = class_getInstanceMethod(self, origSel);
  17. if (!origSel) {
  18. NSLog(@"original method %@ not found for class %@", NSStringFromSelector(origSel), [self class]);
  19. return NO;
  20. }
  21. Method altMethod = class_getInstanceMethod(self, altSel);
  22. if (!altMethod) {
  23. NSLog(@"original method %@ not found for class %@", NSStringFromSelector(altSel), [self class]);
  24. return NO;
  25. }
  26. class_addMethod(self,
  27. origSel,
  28. class_getMethodImplementation(self, origSel),
  29. method_getTypeEncoding(origMethod));
  30. class_addMethod(self,
  31. altSel,
  32. class_getMethodImplementation(self, altSel),
  33. method_getTypeEncoding(altMethod));
  34. method_exchangeImplementations(class_getInstanceMethod(self, origSel), class_getInstanceMethod(self, altSel));
  35. return YES;
  36. }
  37. + (BOOL)swizzleClassMethod:(SEL)origSel withClassMethod:(SEL)altSel
  38. {
  39. Class c = object_getClass((id)self);
  40. return [c swizzleMethod:origSel withMethod:altSel];
  41. }
  42. @end

代码就不用多解释了,下面我们来看如何使用。先看辅助类Foo:
Foo.h

[cpp] view plaincopyprint?

  1. //
  2. //  Foo.h
  3. //  MethodSwizzling
  4. //
  5. //  Created by LuoZhaohui on 1/5/12.
  6. //  Copyright (c) 2012 http://blog.csdn.net/kesalin/. All rights reserved.
  7. //
  8. #import <Foundation/Foundation.h>
  9. // Foo
  10. //
  11. @interface Foo : NSObject
  12. - (void) testMethod;
  13. - (void) baseMethod;
  14. - (void) recursionMethod;
  15. @end
  16. // Bar
  17. //
  18. @interface Bar : Foo
  19. - (void) testMethod;
  20. @end
  21. // Bar(BarCategory)
  22. //
  23. @interface Bar(BarCategory)
  24. - (void) altTestMethod;
  25. - (void) altBaseMethod;
  26. - (void) altRecursionMethod;
  27. @end

Foo.m

[cpp] view plaincopyprint?

  1. //
  2. //  Foo.m
  3. //  MethodSwizzling
  4. //
  5. //  Created by LuoZhaohui on 1/5/12.
  6. //  Copyright (c) 2012 http://blog.csdn.net/kesalin/. All rights reserved.
  7. //
  8. #import "Foo.h"
  9. // Foo
  10. //
  11. @implementation Foo
  12. - (void) testMethod
  13. {
  14. NSLog(@" >> Foo testMethod");
  15. }
  16. - (void) baseMethod
  17. {
  18. NSLog(@" >> Foo baseMethod");
  19. }
  20. - (void) recursionMethod
  21. {
  22. NSLog(@" >> Foo recursionMethod");
  23. }
  24. @end
  25. // Bar
  26. //
  27. @implementation Bar
  28. - (void) testMethod
  29. {
  30. NSLog(@" >> Bar testMethod");
  31. }
  32. @end
  33. // Bar(BarCategory)
  34. //
  35. @implementation Bar(BarCategory)
  36. - (void) altTestMethod
  37. {
  38. NSLog(@" >> Bar(BarCategory) altTestMethod");
  39. }
  40. - (void) altBaseMethod
  41. {
  42. NSLog(@" >> Bar(BarCategory) altBaseMethod");
  43. }
  44. - (void) altRecursionMethod
  45. {
  46. NSLog(@" >> Bar(BarCategory) recursionMethod");
  47. [self altRecursionMethod];
  48. }
  49. @end

下面是具体的使用示例:

[cpp] view plaincopyprint?

  1. int main (int argc, const char * argv[])
  2. {
  3. @autoreleasepool
  4. {
  5. Foo * foo = [[[Foo alloc] init] autorelease];
  6. Bar * bar = [[[Bar alloc] init] autorelease];
  7. NSLog(@"========= Method Swizzling test 1 =========");
  8. NSLog(@" Step 1");
  9. [foo testMethod];
  10. [bar testMethod];
  11. [bar altTestMethod];
  12. NSLog(@" Step 2");
  13. [Bar swizzleMethod:@selector(testMethod) withMethod:@selector(altTestMethod)];
  14. [foo testMethod];
  15. [bar testMethod];
  16. [bar altTestMethod];
  17. NSLog(@"========= Method Swizzling test 2 =========");
  18. NSLog(@" Step 1");
  19. [foo baseMethod];
  20. [bar baseMethod];
  21. [bar altBaseMethod];
  22. NSLog(@" Step 2");
  23. [Bar swizzleMethod:@selector(baseMethod) withMethod:@selector(altBaseMethod)];
  24. [foo baseMethod];
  25. [bar baseMethod];
  26. [bar altBaseMethod];
  27. NSLog(@"========= Method Swizzling test 3 =========");
  28. [Bar swizzleMethod:@selector(recursionMethod) withMethod:@selector(altRecursionMethod)];
  29. [bar recursionMethod];
  30. }
  31. return 0;
  32. }

输出结果为:注意,test 3 中调用了递归调用“自己”的方法,你能理解为什么没有出现死循环么?

========= Method Swizzling test 1 =========
Step 1
>> Foo testMethod
>> Bar testMethod
>> Bar(BarCategory) altTestMethod
Step 2
>> Foo testMethod
>> Bar(BarCategory) altTestMethod
>> Bar testMethod
========= Method Swizzling test 2 =========
Step 1
>> Foo baseMethod
>> Foo baseMethod
>> Bar(BarCategory) altBaseMethod
Step 2
>> Foo baseMethod
>> Bar(BarCategory) altBaseMethod
>> Foo baseMethod
========= Method Swizzling test 3 =========
>> Bar(BarCategory) recursionMethod
>> Foo recursionMethod

test3 解释:在函数体 {} 之间的部分是真正的 IMP,而在这之前的是 SEL。通常情况下,SEL 是与 IMP 匹配的,但在 swizzling 之后,情况就不同了。下图就是调用的时序图。

rentzsch 写了一个完善的开源类 jrswizzle 来处理 Method Swizzling,如果你在工程中使用到 Method Swizzling手法,应该优先使用这个类库,:)。

Refference:

MethodSwizzling:http://www.cocoadev.com/index.pl?ExtendingClasses

jrswizzle:https://github.com/rentzsch/jrswizzle

时间: 2024-10-12 15:42:36

Method Swizzling的相关文章

Objective-C Runtime 运行时之四:Method Swizzling

理解Method Swizzling是学习runtime机制的一个很好的机会.在此不多做整理,仅翻译由Mattt Thompson发表于nshipster的Method Swizzling一文. Method Swizzling是改变一个selector的实际实现的技术.通过这一技术,我们可以在运行时通过修改类的分发表中selector对应的函数,来修改方法的实现. 例如,我们想跟踪在程序中每一个view controller展示给用户的次数:当然,我们可以在每个view controller的

iOS 使用Method Swizzling隐藏Status Bar

在iOS 6中,隐藏Status Bar非常的简单. // iOS 6及以前,隐藏状态栏 [[UIApplication sharedApplication] setStatusBarHidden:YES]; 来到了iOS 7的年代以后,需要在UIViewController中指定: #ifdef __IPHONE_7_0 - (BOOL)prefersStatusBarHidden { return YES; } #endif 并通过下列代码刷新状态栏: if ([viewController

iOS Method Swizzling和分类的妙用AppDelegate轻量化处理

http://www.cocoachina.com/ios/20151117/14167.html 简介 在iOS工程中,AppDelegate往往会有上千行,甚至几千行,这样就会给维护AppDelegate带来诸多麻烦.比方说,老板想在出现HomeViewController之前弹出广告并停顿几秒,这样你就要加入插入广告的逻辑:又比方说,老板想在开始做个请求,判断某个开关是否打开.这样就会在AppDelegate中插入很多相关的不相关的代码. 在AppDelegate中,- (BOOL)app

ios method swizzling

阅读器 iOS开发iOS 本文由TracyYih[博客]翻译自NSHipster的文章Method Swizzling. 在上周associated objects一文中,我们开始探索Objective-C运行时的一些黑魔法.本周我们继续前行,来讨论可能是最受争议的运行时技术:method swizzling. Method swizzling指的是改变一个已存在的选择器对应的实现的过程,它依赖于Objectvie-C中方法的调用能够在运行时进改变——通过改变类的调度表(dispatch tab

iOS执行时与method swizzling

C语言是静态语言,它的工作方式是通过函数调用,这样在编译时我们就已经确定程序怎样执行的.而Objective-C是动态语言,它并不是通过调用类的方法来执行功能,而是给对象发送消息,对象在接收到消息之后会去找匹配的方法来执行.这样的做法就把C语言在编译时的工作挪到了执行时来做,能够获得额外的灵活性. 在Objective-C中有个@selector,在非常多地方被翻译成"选择子".实际上,对于类的实例对象来说,类的方法是用一个数字来代表的,并不是是我们看到的一个长长的带着:这个字符的一串

runtime 第四部分method swizzling

接上一篇 http://www.cnblogs.com/ddavidXu/p/5924597.html 转载来源http://www.jianshu.com/p/6b905584f536 http://southpeak.github.io/2014/10/30/objective-c-runtime-2/ runtime的黑魔法,就是可以实现交换两个方法的实现,这就意味着我们可以修改系统的方法实现. 栗子:当UIViewController及其子类的对象调用viewWillAppear时,都会

iOS黑魔法-Method Swizzling

公司年底要在新年前发一个版本,最近一直很忙,好久没有更新博客了.正好现在新版本开发的差不多了,抽空总结一下.由于最近开发新版本,就避免不了在开发和调试过程中引起崩溃,以及诱发一些之前的bug导致的崩溃.而且项目比较大也很不好排查,正好想起之前研究过的Method Swizzling,考虑是否能用这个苹果的“黑魔法”解决问题,当然用好这个黑魔法并不局限于解决这些问题...... 需求 就拿我们公司项目来说吧,我们公司是做导航的,而且项目规模比较大,各个控制器功能都已经实现.突然有一天老大过来,说我

什么是method swizzling(俗称黑魔法)

之前所说的消息转发虽然功能强大,但需要我们了解并且能更改对应类的源代码,因为我们需要实现自己的转发逻辑.当我们无法触碰到某个类的源代码,却想更改这个类某个方法的实现时,该怎么办呢?可能继承类并重写方法是一种想法,但是有时无法达到目的.这里介绍的是 Method Swizzling ,它通过重新映射方法对应的实现来达到"偷天换日"的目的.跟消息转发相比,Method Swizzling 的做法更为隐蔽,甚至有些冒险,也增大了debug的难度.这里摘抄一个 NSHipster 的例子: #

iOS运行时与method swizzling

C语言是静态语言,它的工作方式是通过函数调用,这样在编译时我们就已经确定程序如何运行的.而Objective-C是动态语言,它并非通过调用类的方法来执行功能,而是给对象发送消息,对象在接收到消息之后会去找匹配的方法来运行.这种做法就把C语言在编译时的工作挪到了运行时来做,可以获得额外的灵活性. 在Objective-C中有个@selector,在很多地方被翻译成"选择子".实际上,对于类的实例对象来说,类的方法是用一个数字来代表的,并非是我们看到的一个长长的带着:这个字符的一串字符串.