您的位置:首页 > 博客中心 > APP开发 >

iOS开发那些事儿(四)the dark arts of the Objective-C runtime

时间:2022-03-11 23:33

一."Black Magic":Method Swizzling 利用 Runtime 特性把一个方法的实现与另一个方法的实现进行替换,也可以用runtime的四维理解——修改Dispatch Table让一个方法的IMP对应到我们指定的IMP上去


 

二.实例说明:比如我们想要在APP中记录每一个ViewController的出现次数


 

三.实例分析

  1. 第一种思路就是在ViewController出现的一瞬间(viewDidAppear)我就用记录工具记录一条日志。 技术分享技术分享
     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的目的(为了想类添加一些代码而不是替换)

  2. 第二种思路就是我们今天的主角method swizzling

 

三.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        
为UIViewController添加分类

 

  解释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 }
直接取代老方法而不是替换

 


 

四.总结

  • Method swizzling is not atomic
  • Changes behavior of un-owned code
  • Possible naming conflicts
  • Swizzling changes the method‘s arguments
  • The order of swizzles matters
  • Difficult to understand (looks recursive)
  • Difficult to debug

相关推荐

电脑软件

本类排行

今日推荐

热门手游