Objective-C的分类/继承的同名方法重写覆盖与解决方案

在做分类和继承的时候,一定要注意的坑,就是分类或者继承里面,不要有同名的方法,否则会被覆盖掉!系统自带的方法名,如deallocviewDidAppear这些也会被覆盖掉,同一主类的不同分类中的普通同名方法调用, 取决于编译的顺序, 后编译的文件中的同名方法会覆盖前面所有的。

验证:有三个类:ViewControllerSonViewController(继承ViewController)SonViewController+Son,他们的代码如下:

//
//  ViewController.m
//  Test
//
//  Created by Hoben on 2020/3/11.
//  Copyright © 2020 Hoben. All rights reserved.
//

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)dealloc {
    NSLog(@"[ViewController] dealloc");
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor whiteColor];
    [self setup];
    
    NSLog(@"[ViewController] viewDidLoad");
    
    
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    
    NSLog(@"[ViewController] viewWillAppear");
}

- (void)setup {
    NSLog(@"[ViewController] setup");
}

@end

//
//  SonViewController.m
//  Test
//
//  Created by Hoben on 2020/3/11.
//  Copyright © 2020 Hoben. All rights reserved.
//

#import "SonViewController.h"

@interface SonViewController ()

@end

@implementation SonViewController

- (void)dealloc {
    NSLog(@"[SonViewController] dealloc");
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self setup];
    
    NSLog(@"[SonViewController] viewDidLoad");
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    
    NSLog(@"[SonViewController] viewWillAppear");
}

- (void)setup {
    NSLog(@"[SonViewController] setup");
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

@end

//
//  ViewController+Son.m
//  Test
//
//  Created by Hoben on 2020/3/11.
//  Copyright © 2020 Hoben. All rights reserved.
//

#import "SonViewController+Son.h"

@implementation SonViewController (Son)

//- (void)dealloc {
//    NSLog(@"[SonViewController+Son] dealloc");
//}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
    [button setTitle:@"退出" forState:UIControlStateNormal];
    button.titleLabel.font = [UIFont systemFontOfSize:14.f];
    [button addTarget:self action:@selector(back) forControlEvents:UIControlEventTouchUpInside];
    [button sizeToFit];
    
    [self.view addSubview:button];
    
    button.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.width / 2);
    
    [self setup];
    
    NSLog(@"[SonViewController+Son] viewDidLoad");
}

- (void)back {
    [self.navigationController popViewControllerAnimated:YES];
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    
    NSLog(@"[SonViewController+Son] viewWillAppear");
}


- (void)setup {
    NSLog(@"[SonViewController+Son] setup");
}

@end

从进入SonViewController+Son到退出这段时间的日志如下:

2020-03-11 21:28:43.447749+0800 Test[71615:692443] [SonViewController+Son] setup
2020-03-11 21:28:43.447954+0800 Test[71615:692443] [ViewController] viewDidLoad
2020-03-11 21:28:43.448821+0800 Test[71615:692443] [SonViewController+Son] setup
2020-03-11 21:28:43.448967+0800 Test[71615:692443] [SonViewController+Son] viewDidLoad
2020-03-11 21:28:43.449099+0800 Test[71615:692443] [ViewController] viewWillAppear
2020-03-11 21:28:43.449183+0800 Test[71615:692443] [SonViewController+Son] viewWillAppear
2020-03-11 21:28:47.558573+0800 Test[71615:692443] [SonViewController] dealloc
2020-03-11 21:28:47.558757+0800 Test[71615:692443] [ViewController] dealloc

这里看上去就有几个很不合理的地方:

  1. SonViewController+Son的setup调用了两次,这是因为ViewControllerSonViewController+Son里面的viewDidLoad各调用了一次,但其他类的setup就没被调用了。

  2. viewDidLoaddealloc这些方法,部分类也没有调用成功。

解决方案:

先利用class获取到所有的子类

// 获取所有子类
- (NSArray *)getAllSubClassNameWithClass:(Class)class {
    NSMutableArray *results = [NSMutableArray array];
    int numClasses;
    Class *classes = NULL;
    numClasses = objc_getClassList(NULL,0);
    if (numClasses > 0) {
        classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);
        numClasses = objc_getClassList(classes, numClasses);
        for (int i = 0; i < numClasses; i++) {
            if (class_getSuperclass(classes[i]) == class){
                [results addObject:classes[i]];
            }
        }
        free(classes);
    }
    return results;
}

再根据class,来获取所有子class的所有SEL(包含分类),找到符合前缀的方法,调用class_getMethodImplementation来获取到相应的IMP,再通过imp获取到方法和SEL。

// 运行所有含有前缀的方法
- (void)performSeletorWithPrefix:(NSString *)prefix objectClass:(Class)objectClass {
    unsigned int methodCount = 0;
    NSArray *subClassArray = [self getAllSubClassNameWithClass:objectClass];
    for (Class class in subClassArray) {
        Method *methodList = class_copyMethodList(class, &methodCount);
        if (methodList && methodCount > 0) {
            for (unsigned int i = 0; i < methodCount; i++) {
                SEL selector = method_getName(methodList[i]);
                NSString *selectorName = NSStringFromSelector(selector);
                if ([selectorName hasPrefix:prefix]) {
                    IMP imp = class_getMethodImplementation(class, selector);
                    if (imp) {
                        void (*func)(id, SEL) = (void *)imp;
                        func(self, selector);
                    }
                }
            }
        }
        if (methodList) {
            free(methodList);
        }
    }
}

调用时,所有的子类都要带前缀:

//
//  ViewController.m
//  Test
//
//  Created by Hoben on 2020/3/11.
//  Copyright © 2020 Hoben. All rights reserved.
//

#import "ViewController.h"
#import "ViewController+Register.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)dealloc {
    NSLog(@"[ViewController] dealloc");
    
    [self performSeletorWithPrefix:@"hobenDealloc" objectClass:self.class];

}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor whiteColor];
    [self setup];
    
    NSLog(@"[ViewController] viewDidLoad");
    
    [self performSeletorWithPrefix:@"hobenViewDidLoad" objectClass:self.class];
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    
    NSLog(@"[ViewController] viewWillAppear");
    [self performSeletorWithPrefix:@"hobenViewWillAppear" objectClass:self.class];

}

- (void)setup {
//    NSLog(@"[ViewController] setup");
    
}

@end

//
//  SonViewController.m
//  Test
//
//  Created by Hoben on 2020/3/11.
//  Copyright © 2020 Hoben. All rights reserved.
//

#import "SonViewController.h"

@interface SonViewController ()

@end

@implementation SonViewController

- (void)hobenDeallocSecond {
    NSLog(@"[SonViewController] dealloc");
}

- (void)hobenViewDidLoadSecond {
        
    NSLog(@"[SonViewController] viewDidLoad");
}

- (void)hobenViewWillAppearSecond {
    
    NSLog(@"[SonViewController] viewWillAppear");
}

- (void)setup {
//    NSLog(@"[SonViewController] setup");
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

@end

//
//  ViewController+Son.m
//  Test
//
//  Created by Hoben on 2020/3/11.
//  Copyright © 2020 Hoben. All rights reserved.
//

#import "SonViewController+Son.h"

@implementation SonViewController (Son)

- (void)hobenDeallocFirst {
    NSLog(@"[SonViewController+Son] dealloc");
}

- (void)hobenViewDidLoadFirst {
    
    [self setup];
    
    NSLog(@"[SonViewController+Son] viewDidLoad");
}

- (void)hobenViewWillAppearFirst {    
    NSLog(@"[SonViewController+Son] viewWillAppear");
}


- (void)setup {
//    NSLog(@"[SonViewController+Son] setup");
}

@end

调用结果如下,可以看到所有子类的所有分类都成功调用了。

2020-03-11 22:31:19.705418+0800 Test[72250:736697] [ViewController] viewDidLoad
2020-03-11 22:31:19.707985+0800 Test[72250:736697] [SonViewController+Son] viewDidLoad
2020-03-11 22:31:19.708093+0800 Test[72250:736697] [SonViewController] viewDidLoad
2020-03-11 22:31:19.708227+0800 Test[72250:736697] [ViewController] viewWillAppear
2020-03-11 22:31:19.710519+0800 Test[72250:736697] [SonViewController+Son] viewWillAppear
2020-03-11 22:31:19.710626+0800 Test[72250:736697] [SonViewController] viewWillAppear
2020-03-11 22:31:20.998335+0800 Test[72250:736697] [ViewController] dealloc
2020-03-11 22:31:21.003280+0800 Test[72250:736697] [SonViewController+Son] dealloc
2020-03-11 22:31:21.003461+0800 Test[72250:736697] [SonViewController] dealloc

总结:写分类的时候,尽量不要用同名方法,除非有必要进行统一(如初始化或者获取调用时机等),不然不会报错,很容易踩坑

你可能感兴趣的:(Objective-C的分类/继承的同名方法重写覆盖与解决方案)