美文网首页开发经验集合从一开始——我的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的分类/继承的同名方法重写覆盖与解决方案

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