Objective-C Method Swizling 的使
既然是一个程序猿,就要不断的学习,虽然没有地位,但是为了家庭,你说要不要生活,哎,算了,开始说我们的话题吧,
Method Swizling --动态交换IMP实现指针
在OC中调用一个方法,其实并不是方法,而是找到内存地址的一个字符串地址,查找是根据@selector 关键字而来的,利用Objective-C的动态特性,可以实现在运行时偷换selector 对应的方法实现,从而达到给方法挂钩的目的
每个类都有一个方法列表,存放着selector的方法和名字实现的映射关系,
IMP 也可以理解为OC中的implements 指针实现。
我们常用的动态交换方法有系统提供给我们几个方法
1. method_exchangeImplementations 交换两个方法
2. class_replaceMethod 替换方法
3. method_setImplementation 设置实现方法
动态交换内部实现方法
看到上面的图,我们是否可以想到两个指针交换数据,我们不能简单的交换指针名字,而是交换了指针地址
对于这一块的东西理解的也是毛坯,等有时间继续了解一下,目前我根据我看到的资料,分享一下简单的使用
我们来获取控制器的viewdidload 方法 viewWillAppear 出现的次数,我们进行动态的交换方法,
我们先写一个动态交换方法
第一步,我们新建一个Category ,名字为 UIViewController+AOP
我们来写一个静态实例交换方法
//originalSelector 原来方法
// swizzleSelector 动态运行时的交换方法
void swizzleMethod(Class class,SEL originalSelector,SEL swizzleSelector){
//class_getInstanceMethod返回 class的名称为selector的方法
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzleSelector);
//method_getImplementation 返回method的实现指针
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if(didAddMethod){
//class_replaceMethod 替换函数实现 函数 originalMethod 用swizzleSelector 替换
class_replaceMethod(class, swizzleSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}else{
//交换两个IMP是实现指针
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
以上都有注释,这里就不多写明了,下面我们看下系统提供的交换方法的API,
// 交换两个方法,传入的参数是两个@selector 方法
OBJC_EXPORT void method_exchangeImplementations(Method m1, Method m2)
__OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);
//替换方法
参数含义
Class class : 需要替换方法的类名
SEL name : 需要替换的方法名字
IMP imp : 需要实现的方法名字
const char * types :原方法的名字
OBJC_EXPORT IMP class_replaceMethod(Class cls, SEL name, IMP imp,
const char *types)
__OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);
//设置实现指针
参数 :
Method m: 方法名字
IMP imp : 实现指针
OBJC_EXPORT IMP method_setImplementation(Method m, IMP imp)
__OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);
为什么我们要这么麻烦写这么一段代码,接手维护的项目大多都不好管理,需要进行重构,我们尽可能的会用少量的代码简化我的方法体
下面我们继续实现类中一定会调用的方法load 方法
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
swizzleMethod(class, @selector(viewDidLoad), @selector(aop_viewDidLoad:));
swizzleMethod(class, @selector(viewDidAppear:), @selector(aop_viewDidAppear:));
swizzleMethod(class, @selector(viewWillAppear:), @selector(aop_viewWillAppear:));
swizzleMethod(class, @selector(viewWillDisappear:), @selector(aop_viewWillDisAppear:));
});
}
问题:+(void)load VS +(void)initialize
每个类的这两个方法都会被Objective-C运行时系统自动调用,+load 方法是在一个类最开始调用的,+initialize 是在应用中第一次调用该类或者他的实例的方式之前调用,这两个方法都可以使用,只有实现了才会被执行,
既然是分类,就会全局监听,+load 能够保证在类初始化的时候会被加载,通过这个操作可以做一些统一的操作,但是+initialize 并不能保证什么时候被调用,有可能永远也不会被调用,如果应用程序从未直接给该类发送任何消息,则无法调用
在调用load 方法的时候,我们通常是通过
static dispatch_once_t onceToken;
dispatch_once(&onceToken,^{
//我们采取了一个预防措施,防止运行时多次被执行,确保代码即使使用在多线程环境下也只会被执行一次,我们优先使用GCD
});
调用_cmd
下面这段代码不会影响执行,也不会进入死循环,
aop_viewWillAppear 这个方法会在运行时进入到协同的viewWillAppear 方法执行,但是我们不能在这个里面调用viewWillAppear,以免造成无法控制的危险
- (void)aop_viewWillAppear:(BOOL)animated{
[self aop_viewWillAppear:animated];
NSLog(@"111111111111");
}
下面我们给出源代码整体的实现思路,
#import <UIKit/UIKit.h>
@interface UIViewController (AOP)
- (void)aop_viewWillAppear:(BOOL)animated;
- (void)aop_viewDidAppear:(BOOL)animated;
- (void)aop_viewDidLoad:(BOOL)animated;
- (void)aop_viewWillDisAppear:(BOOL)animated;
@end
#import "UIViewController+AOP.h"
#import <objc/runtime.h>
@implementation UIViewController (AOP)
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
swizzleMethod(class, @selector(viewDidLoad), @selector(aop_viewDidLoad:));
swizzleMethod(class, @selector(viewDidAppear:), @selector(aop_viewDidAppear:));
swizzleMethod(class, @selector(viewWillAppear:), @selector(aop_viewWillAppear:));
swizzleMethod(class, @selector(viewWillDisappear:), @selector(aop_viewWillDisAppear:));
});
}
void swizzleMethod(Class class,SEL originalSelector,SEL swizzleSelector){
//class_getInstanceMethod返回 class的名称为selector的方法
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzleSelector);
//method_getImplementation 返回method的实现指针
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if(didAddMethod){
//class_replaceMethod 替换函数实现 函数 originalMethod 用swizzleSelector 替换
class_replaceMethod(class, swizzleSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}else{
//交换两个IMP是实现指针
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
- (void)aop_viewWillAppear:(BOOL)animated{
[self aop_viewWillAppear:animated];
NSLog(@"111111111111");
}
- (void)aop_viewDidAppear:(BOOL)animated{
[self aop_viewDidAppear:animated];
NSLog(@"22222222222");
}
- (void)aop_viewDidLoad:(BOOL)animated{
[self aop_viewDidLoad:animated];
NSLog(@"33333333333333");
if([self isKindOfClass:[UINavigationController class] ]){
UINavigationController * nav =(UINavigationController *)self;
nav.navigationBar.translucent = NO;
nav.navigationBar.tintColor = [UIColor whiteColor];
nav.navigationBar.barTintColor = [UIColor greenColor];
NSDictionary * titleAtt = @{NSForegroundColorAttributeName:[UIColor whiteColor]};
[[UINavigationBar appearance]setTitleTextAttributes:titleAtt];
[[UIBarButtonItem appearance ] setBackButtonTitlePositionAdjustment:UIOffsetMake(0, -60) forBarMetrics:UIBarMetricsDefault];
}
self.navigationController.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)self;
}
- (void)aop_viewWillDisAppear:(BOOL)animated{
[self aop_viewWillDisAppear:animated];
#ifndef DEBUG
NSLog(@"44444444");
#endif
}
@end
以上是整体的代码
这种方法可以来控制我们的代码减少代码结构,
文章如有错误还望大神给予指正,在这里只是学习一下,
iOS 学习交流群:392633290 加群备注:(简书)
Apple 官方的 Objective-C Runtime Reference objc/runtime
同时附上原文链接:
OC动态运行时使用Method Swizling