标签:
一."Black Magic":Method Swizzling 利用 Runtime 特性把一个方法的实现与另一个方法的实现进行替换,也可以用runtime的四维理解——修改Dispatch Table让一个方法的IMP对应到我们指定的IMP上去
二.实例说明:比如我们想要在APP中记录每一个ViewController的出现次数
三.实例分析:
1 @implementation MyViewController () 2 3 - (void)viewDidAppear:(BOOL)animated 4 { 5 [super viewDidAppear:animated]; 6 7 // Custom code 8 9 // Logging 10 [Logging logWithEventName:@“my view did appear”]; 11 } 12 13 14 - (void)myButtonClicked:(id)sender 15 { 16 // Custom code 17 18 // Logging 19 [Logging logWithEventName:@“my button clicked”]; 20 }
*缺点就在于,我要记录所有的VC那么我就要在所有VC中去手写这些代码?那聪明的你一定会想到Catagory。但是仔细想一想你需要继承UIViewController、UITableViewController、UICollectionViewController所有这些 VC添加类别,而且违背了苹果设计Catagory的目的(为了想类添加一些代码而不是替换)
三.Demo:
1 #import <objc/runtime.h> 2 3 @implementation UIViewController (Tracking) 4 5 + (void)load { 6 //保证只替换一次 7 static dispatch_once_t onceToken; 8 dispatch_once(&onceToken, ^{ 9 Class class = [self class]; 10 SEL originalSelector = @selector(viewWillAppear:); 11 SEL swizzledSelector = @selector(xxx_viewWillAppear:); 12 13 Method originalMethod = class_getInstanceMethod(class, originalSelector); 14 Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); 15 16 BOOL didAddMethod = 17 class_addMethod(class, 18 originalSelector, 19 method_getImplementation(swizzledMethod), 20 method_getTypeEncoding(swizzledMethod)); 21 22 if (didAddMethod) { 23 class_replaceMethod(class, 24 swizzledSelector, 25 method_getImplementation(originalMethod), 26 method_getTypeEncoding(originalMethod)); 27 } else { 28 method_exchangeImplementations(originalMethod, swizzledMethod); 29 } 30 }); 31 } 32 33 #pragma mark - Method Swizzling 34 35 - (void)xxx_viewWillAppear:(BOOL)animated { 36 //这里需要解释一下,因为我们在ViewwillAppear中需要去call父类的实现。这个这行代码调用的时候xxx_viewWillAppear与viewWillAppear方法已经替换。所以不是表面看上去的递归形式 37 [self xxx_viewWillAppear:animated]; 38 39 [Logging logWithEventName:NSStringFromClass([self class])]; 40 } 41 42 @end
解释1.这里唯一可能需要解释的是 class_addMethod
。要先尝试添加原 selector 是为了做一层保护,因为如果这个类没有实现 originalSelector
,但其父类实现了,那 class_getInstanceMethod
会返回父类的方法。这样 method_exchangeImplementations
替换的是父类的那个方法,这当然不是你想要的。所以我们先尝试添加 orginalSelector
,如果已经存在,再用 method_exchangeImplementations
把原方法的实现跟新的方法实现给交换掉
解释2:Swizzling总是在+load中执行,Because method swizzling affects global state, it is important to minimize the possibility of race conditions. +load is guaranteed to be loaded during class initialization, which provides a modicum of consistency for changing system-wide behavior. By contrast, +initialize provides no such guarantee of when it will be executed—in fact, it may never be called, if that class is never messaged directly by the app.da大概意思是因为方+load是保证是在类初始化加载,从而改变系统行为提供了些许的一致性。相比之下,没有提供这样的保证+init(),如果APP没有直接调用,可能永远不会执行。
1 void (gOriginalViewDidAppear)(id, SEL, BOOL); 2 3 void newViewDidAppear(UIViewController *self, SEL _cmd, BOOL animated) 4 { 5 // call original implementation 6 gOriginalViewDidAppear(self, _cmd, animated); 7 8 // Logging 9 [Logging logWithEventName:NSStringFromClass([self class])]; 10 } 11 12 + (void)load 13 { 14 Method originalMethod = class_getInstanceMethod(self, @selector(viewDidAppear:)); 15 gOriginalViewDidAppear = (void *)method_getImplementation(originalMethod); 16 17 if(!class_addMethod(self, @selector(viewDidAppear:), (IMP) newViewDidAppear, method_getTypeEncoding(originalMethod))) { 18 method_setImplementation(originalMethod, (IMP) newViewDidAppear); 19 } 20 }
四.总结:What are the Dangers of Method Swizzling in Objective C?
iOS开发那些事儿(四)the dark arts of the Objective-C runtime
标签:
原文地址:http://www.cnblogs.com/conorBlogs/p/5498283.html