美文网首页OC 底层
分类Category重写原类方法的一些注意事项

分类Category重写原类方法的一些注意事项

作者: ImmortalSummer | 来源:发表于2019-10-10 15:54 被阅读0次

目录
1.分类如何重写类的原有方法?
2.分类重写类的原有方法后, 类的原有方法在调用时, 谁会响应?
3.分类在重写类的原有方法时, 如何调用原方法?
4.其实不用引用分类的头文件, 分类中的方法也已经添加到对应的类中了

1.分类如何重写类的原有方法?

分类中重写这个方法即可, 重写后会报警告:

Category is implementing a method which will also be implemented by its primary class

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
//your code
#pragma clang diagnostic pop

可以去掉警告.

2.分类重写类的原有方法后, 类的原有方法在调用时, 谁会响应?

我们经常会遇到分类中重写了原类方法导致原类中的方法被覆盖了,category的底层实现是在加载的时候,把category中的方法添加到原类的方法列表中,当调用方法时会遍历方法列表找到对应的响应子就返回,不再向下遍历。因为category的优先级高于类的优先级,使得原类中的选择子遍历不到。
链接:https://www.jianshu.com/p/e6bb64beb378

即:
classA 有一个方法 -(void)test;
classA的分类category1 重写了这个方法-(void)test;
这时候 我们 [[classA new] test]; 这样调用时, 响应的是category1的实现.

//  MyView.m
#import "MyView.h"
@implementation MyView
-(void)testFonction{
    NSLog(@"MyView run!");
}
@end
//  MyView+category1.m
#import "MyView+category1.h"
@implementation MyView (category1)
-(void)testFonction{
    NSLog(@"MyView category1 run!");
}
@end
/*
     输出: MyView category1 run!
*/
MyView *myView = [MyView new];
[myView testFonction];

3.分类在重写类的原有方法时, 如何调用原方法?

分类在重写类的原有方法时, 是无法使用super 调用原方法的, 可以去遍历原类的方法列表, 去查询最后一个同名方法的selector
即为原类的方法, 然后获取方法的指针地址来执行方法.

为什么是最后一个同名方法是原类的方法?

如果classA 的分类: category1 重写了 test方法, 遍历classA的方法类别时的顺序是这样的:
category1 重写的test
classA 的test
执行test方法时, 找到第一个(分类的实现)就会停止遍历, 返回并相应, 我们需要手动遍历找到最后一个(即原类的实现)并获取的指针地址来执行它

例如ios13 presentViewController出来的控制器样式变了, 我们需要在跳转前给目标控制器设置modalPresentationStyle 属性的值为 UIModalPresentationFullScreen. 我们如果想要统一处理, 就可以通过UIViewController添加分类来重写控制器的presentViewController:animated:completion:方法.

在分类中实现的时候:
1.给目标控制器的modalPresentationStyle 属性的值设置为 UIModalPresentationFullScreen
2.执行原类的presentViewController:animated:completion:方法实现跳转.

分类重写原类方法时, 类的方法列表, 分类的重写会排在前边, 原方法会排在最后. 执行时, 只有第一个会响应, 即原方法不会响应, 我们想要执行原方法, 无法使用super, 需要遍历原类的方法列表, 找到最后一个同名方法(即原方法), 然后获取方法的指针并执行

#import <objc/runtime.h>

#import "UIViewController+PresentViewControllerOnIOS13.h"

@implementation UIViewController (PresentViewControllerOnIOS13)


#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"

-(void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion{
    @try {
        
        viewControllerToPresent.modalPresentationStyle = UIModalPresentationFullScreen;
        
        //分类重写原类方法时, 类的方法列表, 分类的重写会排在前边, 原方法会排在最后. 执行时, 只有第一个会响应, 即原方法不会响应, 我们想要执行原方法, 无法使用super, 需要遍历原类的方法列表, 找到最后一个同名方法(即原方法), 然后获取方法的指针并执行
        
        //NSLog(@"methodName begin----------------------------------------");
        u_int count;
//注意:要从UIViewController中获取方法列表, 而不能self. 因为self可能是UIViewController的子类(例如:UITabBarController), 子类中是遍历不到父类方法的
        Method *methodsArr = class_copyMethodList([UIViewController class], &count);
        NSInteger index = 0;
        for (int i = 0; i < count; i++) {
            SEL method = method_getName(methodsArr[i]);
            NSString *methodName = [NSString stringWithCString:sel_getName(method) encoding:NSUTF8StringEncoding];
            //NSLog(@"%@",methodName);
            if ([methodName isEqualToString:@"presentViewController:animated:completion:"]) {
                index = i;
                //NSLog(@"methodName get ---> %d",i);
            }
        }
        //NSLog(@"methodName end----------------------------------------");
        
        SEL sel = method_getName(methodsArr[index]);
        IMP imp = method_getImplementation(methodsArr[index]);
        
        ((void (*)(id, SEL, id, BOOL, id))imp)(self, sel, viewControllerToPresent, flag, completion);
        
    } @catch (NSException *exception) {
        NSLog(@"presentViewController error %@",exception);
    }
}

#pragma clang diagnostic pop

@end

以上代码需要注意的地方
1.Method *methodsArr = class_copyMethodList([UIViewController class], &count); 遍历方法时, 要从UIViewController中获取方法列表, 而不能self. 因为self可能是UIViewController的子类(例如:UITabBarController), 子类中是遍历不到父类方法的

2.((void (*)(id, SEL, id, BOOL, id))imp)(self, sel, viewControllerToPresent, flag, completion); block的参数类型传id即可

4.其实不用引用分类的头文件, 分类中的方法也已经添加到对应的类中了

//  MyView.h
#import <UIKit/UIKit.h>
@interface MyView : UIView
-(void)testFonction;
@end

//  MyView.m
#import "MyView.h"
@implementation MyView
-(void)testFonction{
    NSLog(@"MyView run!");
}
@end
//  MyView+category1.h
#import "MyView.h"
@interface MyView (category1)
-(void)test2;
@end

//  MyView+category1.m
#import "MyView+category1.h"
@implementation MyView (category1)
-(void)testFonction{
    NSLog(@"MyView category1 run!");
}
-(void)test2{
    NSLog(@"test2");
}
@end
#import "ViewController.h"
#import <objc/runtime.h>
#import "MyView.h"
//#import "MyView+category1.h"

@interface ViewController ()

@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];

    //如果不对分类的头文件做引用(#import "MyView+category1.h"), 一下方法不可执行, 但是如果遍历myView的方法列表, 我们发现myView的方法列表其实是含有test2方法的, 只是我们没有引用头文件, 无法得到方法的声明, 也就无法主动条用了.
    
    //[myView test2];
    
    //结论:
    //其实不用引用分类的头文件, 分类中的方法也已经添加到对应的类中了
    //分类重写原类方法, 不需要头文件引用, 即可全局起作用.
    //引用分类的头文件, 只是为了得到分类新增的方法的声明.
    //所以分类重写原类方法是十万分谨慎, 除非你知道你在做什么(你想对全局所有的类做改变,包括参与这个项目的其他同学写的代码也会受你的重写影响), 否则可能会影响整个工程.
    
    NSLog(@"methodName begin----------------------------------------");
    u_int count;
    Method *methodsArr = class_copyMethodList([myView class], &count);
    NSInteger index = 0;
    for (int i = 0; i < count; i++) {
        SEL method = method_getName(methodsArr[i]);
        NSString *methodName = [NSString stringWithCString:sel_getName(method) encoding:NSUTF8StringEncoding];
        NSLog(@"%@",methodName);
        if ([methodName isEqualToString:@"test2"]) {
            index = i;
            NSLog(@"methodName get at index ---> %d",i);
        }
    }
    NSLog(@"methodName end----------------------------------------");
    
    /*
     14:49:47.241411 [37785:1488916] methodName begin----------------------------------------
     14:49:47.241492 [37785:1488916] testFonction
     14:49:47.241629 [37785:1488916] testFonction
     14:49:47.241706 [37785:1488916] test2
     14:49:50.258245 [37785:1488916] methodName get ---> 2
     14:49:50.258451 [37785:1488916] methodName end----------------------------------------
     */
}
@end

通过打印我们也发现, 我们遍历的是myView的方法列表(Method *methodsArr = class_copyMethodList([myView class], &count);), 虽然MyView是UIView的子类, 但是UIView的方法并没有被打印出来.
另外, testFonction方法打印了两次, 第一次就是分类实现的. 第二次是原类实现的. 虽然我们没有引用分类的头文件, 但是分类添加的test2也是可以遍历到的.

相关文章

网友评论

    本文标题:分类Category重写原类方法的一些注意事项

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