目录
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也是可以遍历到的.
网友评论