Objective-C的分类/继承的同名方法重写覆盖与解决方案
2020-03-11 本文已影响0人
肠粉白粥_Hoben
在做分类和继承的时候,一定要注意的坑,就是分类或者继承里面,不要有同名的方法,否则会被覆盖掉!系统自带的方法名,如dealloc
、viewDidAppear
这些也会被覆盖掉,同一主类的不同分类中的普通同名方法调用, 取决于编译的顺序, 后编译的文件中的同名方法会覆盖前面所有的。
验证:有三个类:ViewController
、SonViewController(继承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
这里看上去就有几个很不合理的地方:
-
SonViewController+Son
的setup调用了两次,这是因为ViewController
和SonViewController+Son
里面的viewDidLoad
各调用了一次,但其他类的setup就没被调用了。 -
viewDidLoad
、dealloc
这些方法,部分类也没有调用成功。
解决方案:
先利用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
总结:写分类的时候,尽量不要用同名方法,除非有必要进行统一(如初始化或者获取调用时机等),不然不会报错,很容易踩坑