美文网首页开发经验集合从一开始——我的iOS学习之路
Objective-C的分类/继承的同名方法重写覆盖与解决方案

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

作者: 肠粉白粥_Hoben | 来源:发表于2020-03-11 22:33 被阅读0次

在做分类和继承的时候,一定要注意的坑,就是分类或者继承里面,不要有同名的方法,否则会被覆盖掉!系统自带的方法名,如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的分类/继承的同名方法重写覆盖与解决方案

    在做分类和继承的时候,一定要注意的坑,就是分类或者继承里面,不要有同名的方法,否则会被覆盖掉!系统自带的方法名,如...

  • 重写(=覆盖)、重载

    override(重写、覆盖): 子类在继承父类时,重写(重新实现)父类中的方法。 重写(覆盖)的规则: 重写方法...

  • 2018-07-10

    JAVA重写的条件 条件: 子类继承父类,并重写父类的方法 1、 重写要求子类中的覆盖方法与父类中被覆盖的方法有...

  • 1.7 继承相关特性

    本小节知识点: 方法重写 继承中方法调用的顺序 继承的注意事项 1.方法重写 在子类中实现与父类中同名的方法,称之...

  • OC中重要的一些概念<一>

    继承 方法重写 在子类中实现与父类中同名的方法,称之为方法重写; 重写以后当给子类发送这个消息的时候,执行的是在子...

  • JAVA学习笔记(四)

    继承要解决的问题什么是继承关系子类继承了父类的哪些成员方法覆盖方法重载(Overload)和方法覆盖(方法重写 O...

  • Java自学-接口与继承 重写

    Java 重写方法 子类可以继承父类的对象方法 在继承后,重复提供该方法,就叫做方法的重写 又叫覆盖 Overri...

  • OC 类的继承 方法重载重写

    OC 类的继承 方法重载重写 资料一: 一、类的继承 Objective-c中类的继承与C++类似,不同的是Obj...

  • java系列6:多态(Polymorphism)

    解决了程序的扩展问题。 一、方法覆盖(方法重写) 1、方法覆盖(方法重写) 在类的继承体系结构中,如果子类中出现了...

  • 继承,重写,覆盖,重载,多态区分

    重写(override) = 覆盖一般是子类继承父类后,重写其中的方法。重写有几个需要注意的规则:1、重写方法的参...

网友评论

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

    本文链接:https://www.haomeiwen.com/subject/rmwhjhtx.html