阅读 117

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

前言

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

问题

其实说白了,如果复写父类的方法,可能会引发父类方法的内容的变更,这样是极其危险的。为了验证这样的问题,特意写了一个分类:

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

@implementation UIViewController (Add)

- (void)viewDidLoad{

    NSLog(@"分类 viewDidLoad");
    
}

复制代码

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

#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

复制代码

结果如下:

运行结果

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

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

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

分析

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

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

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

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

结论

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

  • 分类复写扩展类的方法,会造成扩展类原始方法的覆盖,造成整个扩展类原始方法的变更,对整个扩展类的功能造成巨大影响,是一种很危险的行为;
  • 分类的本质是扩展功能,而不是复写原始功能,如果需要对原始方法进行扩展,可以考虑使用继承或者 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里面的方法被调用了。

关注下面的标签,发现更多相似文章
评论