iOS 中不要在分类中扩展类的方法

前言

说的比较直白,iOS 中有个好东西,都知道那就是分类Category,有了这个分类咱们可以轻松的给基类添加一些功能,更加灵活的添加咱们想要的功能。可是在使用的时候,咱们要注意一点,就是避免去复写父类的方法,若是不当心复写了父类方法,可能由此变得乱套了。bash

问题

其实说白了,若是复写父类的方法,可能会引起父类方法的内容的变动,这样是极其危险的。为了验证这样的问题,特地写了一个分类:ui

#import "UIViewController+Add.h"
#import <objc/runtime.h>

@implementation UIViewController (Add)

- (void)viewDidLoad{

    NSLog(@"分类 viewDidLoad");
    
}

复制代码

而主类中,咱们不导入这个分类,看一下运行结果:spa

#import "ViewController.h"
#import <objc/runtime.h>

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    NSLog(@"基类的 ViewDidLoad 方法");
    
    id viewController = objc_getClass("ViewController");
    
    int outCount,i;
    
    Method *methodList = class_copyMethodList(viewController, &outCount);
    
    for (i = 0; i < outCount; i++) {
        Method method = methodList[i];
        NSLog(@"current method is :%@",NSStringFromSelector(method_getName(method)));
    }

}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


@end

复制代码

结果以下:3d

运行结果

看一下你会发现,在没有导入分类头文件的状况下,分类中的方法被调用了。为了解释这个问题,咱们将 controller中的方法用runtime 打印出来,确实只有两个方法。根据运行的结果,发现子类的 viewDidLoad先被调用,其次父类的 viewDidLoad被调用,仿佛在父类的viewDidLoad中添加了分类的内容。这里有两点疑问:code

  • 没有导入分类头文件的状况下为何可以自动加载并调起分类中的方法;
  • 在方法列表中确实只有两个方法,那么 Category 中复写的方法跟父类到底是什么关系,是复写仍是功能的添加

为了搞懂这两点,咱们继续往下深扒~cdn

分析

从打印出来的方法咱们可以看出来ViewController中确实只有一个 ViewDidLoad 方法,因此在methodLists中只有一个与之对应的 SEL。其次,在没有引入头文件的状况下,可以自动调用分类中的方法,只能说明一点,那就是父类中的 ViewDidLoad已经受到分类方法的影响,已经被分类复写,这里说判定是复写是由于在去除[super viewDidLoad]以后,分类中的分类 viewLoad提示已经不在了。blog

这里解释了第二点,分类复写的方法与父类的方法的关系是覆盖关系,分类方法覆盖父类的方法。继承

那么第一点在没有导入头文件的状况下,为何分类的方法会被引用呢?get

这一点其实也很好解释,由于不论有没有import category 的头文件,均可以成功调用category的方法,在runtime加载成功以后,Category 已经将扩展中的复写的方法对原方法进行了替换,import只是帮助了编译检查和连接过程。关于runtime 对于 category 的加载流程咱们能够参考这篇文章objc category的秘密string

结论

对于以上分析,总结来讲就是:

  • 分类复写扩展类的方法,会形成扩展类原始方法的覆盖,形成整个扩展类原始方法的变动,对整个扩展类的功能形成巨大影响,是一种很危险的行为;
  • 分类的本质是扩展功能,而不是复写原始功能,若是须要对原始方法进行扩展,能够考虑使用继承或者 hook 方法进行;
  • 不管有没有导入分类头文件,runtime在加载完成以后会将分类的扩展方法加入到 methodList方法列表中,导入头文件的过程只是保证编译检查成功以及连接过程顺利完成;

扩展(一个类中添加多个分类相同方法,这些分类的方法调用顺序)

在一个类中添加多个不一样分类的相同方法,以下:

#import "LCView+AddOne.h"

@implementation LCView (AddOne)

- (void)testMethod{
    
    NSLog(@"from category one");
}


@end

复制代码
#import "LCView+AddTwo.h"

@implementation LCView (AddTwo)

- (void)testMethod{
    
    NSLog(@"from category two");
}


@end

复制代码
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    id testView = objc_getClass("LCView");
    
    unsigned int outCount,i;
    
    Method *methodList = class_copyMethodList(testView, &outCount);
    
    for (i = 0; i < outCount; i++) {
        Method method = methodList[i];
        NSLog(@"current method is :%@",NSStringFromSelector(method_getName(method)));
    }
    
    LCView *test = [LCView new];
    
    [test testMethod];

}

复制代码

运行以后的结果以下:

可见,若是多个分类扩展添加同一个方法的话,当前methodList中会同时有多个 SEL,而真正调用的时候调用的 SEL 是其中一个,这个调用顺序与源文件的编译顺序有关,他们的关系是根据buildPhases->Compile Sources里面的顺序从上至下编译的,换句话说就是,越排在后面的分类方法将会被实际调用,如图:

这里分类AddTwo里面的方法被调用了。

相关文章
相关标签/搜索