面向切面编程参考:React Native面向切面编程ios
ObjC
中实现 AOP
最直接的方法就是使用 Runtime
中的 Method Swizzling
。使用Aspects
, 能够不须要繁琐的手工调用 Method Swizzling
。git
所谓 AOP 其实就是给你的程序提供一个可拆卸的组件化能力。好比你的 APP 须要用到事件统计功能, 不管你是用 UMeng, Google Analytics, 仍是其余的统计平台等等, 你应该都会写过相似的代码:github
- (void)viewDidLoad {
[super viewDidLoad];
[Logger log:@"View Did Load"];
// 下面初始化数据
}
复制代码
在视图控制器开始加载的时候,用 Logger 类记录一个统计事件。 其实 viewDidLoad 方法自己的逻辑并非为了完成统计,而是进行一些初始化操做。这就致使了一个设计上的瑕疵, 数据统计的代码和咱们实际的业务逻辑代码混杂在一块儿了。随着业务逻辑代码不断增多,相似的混杂也会愈来愈多,这样的耦合势必会增长维护的成本。AOP 其实就是在不影响程序总体功能的状况下,将 Logger 这样的逻辑,从主业务逻辑中抽离出来的能力。有了 AOP 以后, 咱们的业务逻辑代码就变成了这样:编程
- (void)viewDidLoad {
[super viewDidLoad];
// 下面初始化数据
}
复制代码
这里再也不会出现 Logger 的统计逻辑的代码,可是统计功能依然是生效的。 固然,不出如今主业务代码中,不表明统计代码就消失了。 而是用 AOP 模式 hook 到别的地方去了。swift
优势:数组
缺点:安全
网上有一篇文章iOS---防止UIButton重复点击的三种实现方式,通过实践发现文章能够做为一个demo来演示,在真实的项目开发中是不实用的。由于sendAction:to:forEvent:
方法是UIControl的方法,全部继承自UIControl的类的这个方法都会被替换,好比UISwitch。下面是针对这篇文章的改进版,确保只有UIButton的改方法被HOOK:bash
#import <UIKit/UIKit.h>
@interface UIButton (FixMultiClick)
@property (nonatomic, assign) NSTimeInterval clickInterval;
@end
#import "UIButton+FixMultiClick.h"
#import <objc/runtime.h>
#import <Aspects/Aspects.h>
@interface UIButton ()
@property (nonatomic, assign) NSTimeInterval clickTime;
@end
@implementation UIButton (FixMultiClick)
-(NSTimeInterval)clickTime {
return [objc_getAssociatedObject(self, _cmd) doubleValue];
}
-(void)setClickTime:(NSTimeInterval)clickTime {
objc_setAssociatedObject(self, @selector(clickTime), @(clickTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSTimeInterval)clickInterval {
return [objc_getAssociatedObject(self, _cmd) doubleValue];
}
-(void)setClickInterval:(NSTimeInterval)clickInterval {
objc_setAssociatedObject(self, @selector(clickInterval), @(clickInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
+(void)load {
[UIButton aspect_hookSelector:@selector(sendAction:to:forEvent:)
withOptions:AspectPositionInstead
usingBlock:^(id<AspectInfo> info){
UIButton *obj = info.instance;
if(obj.clickInterval <= 0){
[info.originalInvocation invoke];
}
else{
if ([NSDate date].timeIntervalSince1970 - obj.clickTime < obj.clickInterval) {
return;
}
obj.clickTime = [NSDate date].timeIntervalSince1970;
[info.originalInvocation invoke];
}
} error:nil];
}
@end
复制代码
crash的具体几种状况函数
解决思路: HOOK
系统方法,替换为自定义的安全方法组件化
#import <Foundation/Foundation.h>
@interface NSArray (Aspect)
@end
#import "NSArray+Aspect.h"
#import <objc/runtime.h>
@implementation NSArray (Aspect)
/**
* 对系统方法进行替换
*
* @param systemSelector 被替换的方法
* @param swizzledSelector 实际使用的方法
* @param error 替换过程当中出现的错误消息
*
* @return 是否替换成功
*/
+ (BOOL)systemSelector:(SEL)systemSelector customSelector:(SEL)swizzledSelector error:(NSError *)error{
Method systemMethod = class_getInstanceMethod(self, systemSelector);
if (!systemMethod) {
return NO;
}
Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
if (!swizzledMethod) {
return NO;
}
if (class_addMethod([self class], systemSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {
class_replaceMethod([self class], swizzledSelector, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
}
else{
method_exchangeImplementations(systemMethod, swizzledMethod);
}
return YES;
}
/**
NSArray 是一个类簇
*/
+(void)load{
[super load];
// 越界:初始化的空数组
[objc_getClass("__NSArray0") systemSelector:@selector(objectAtIndex:)
customSelector:@selector(emptyObjectIndex:)
error:nil];
// 越界:初始化的非空不可变数组
[objc_getClass("__NSSingleObjectArrayI") systemSelector:@selector(objectAtIndex:)
customSelector:@selector(singleObjectIndex:)
error:nil];
// 越界:初始化的非空不可变数组
[objc_getClass("__NSArrayI") systemSelector:@selector(objectAtIndex:)
customSelector:@selector(safe_arrObjectIndex:)
error:nil];
// 越界:初始化的可变数组
[objc_getClass("__NSArrayM") systemSelector:@selector(objectAtIndex:)
customSelector:@selector(safeObjectIndex:)
error:nil];
// 越界:未初始化的可变数组和未初始化不可变数组
[objc_getClass("__NSPlaceholderArray") systemSelector:@selector(objectAtIndex:)
customSelector:@selector(uninitIIndex:)
error:nil];
// 越界:可变数组
[objc_getClass("__NSArrayM") systemSelector:@selector(objectAtIndexedSubscript:)
customSelector:@selector(mutableArray_safe_objectAtIndexedSubscript:)
error:nil];
// 越界vs插入:可变数插入nil,或者插入的位置越界
[objc_getClass("__NSArrayM") systemSelector:@selector(insertObject:atIndex:)
customSelector:@selector(safeInsertObject:atIndex:)
error:nil];
// 插入:可变数插入nil
[objc_getClass("__NSArrayM") systemSelector:@selector(addObject:)
customSelector:@selector(safeAddObject:)
error:nil];
}
- (id)safe_arrObjectIndex:(NSInteger)index{
if (index >= self.count || index < 0) {
NSLog(@"this is crash, [__NSArrayI] check index (objectAtIndex:)") ;
return nil;
}
return [self safe_arrObjectIndex:index];
}
- (id)mutableArray_safe_objectAtIndexedSubscript:(NSInteger)index{
if (index >= self.count || index < 0) {
NSLog(@"this is crash, [__NSArrayM] check index (objectAtIndexedSubscript:)") ;
return nil;
}
return [self mutableArray_safe_objectAtIndexedSubscript:index];
}
- (id)singleObjectIndex:(NSUInteger)idx{
if (idx >= self.count) {
NSLog(@"this is crash, [__NSSingleObjectArrayI] check index (objectAtIndex:)") ;
return nil;
}
return [self singleObjectIndex:idx];
}
- (id)uninitIIndex:(NSUInteger)idx{
if ([self isKindOfClass:objc_getClass("__NSPlaceholderArray")]) {
NSLog(@"this is crash, [__NSPlaceholderArray] check index (objectAtIndex:)") ;
return nil;
}
return [self uninitIIndex:idx];
}
- (id)safeObjectIndex:(NSInteger)index{
if (index >= self.count || index < 0) {
NSLog(@"this is crash, [__NSArrayM] check index (objectAtIndex:)") ;
return nil;
}
return [self safeObjectIndex:index];
}
- (void)safeInsertObject:(id)object atIndex:(NSUInteger)index{
if (index>self.count) {
NSLog(@"this is crash, [__NSArrayM] check index (insertObject:atIndex:)") ;
return ;
}
if (object == nil) {
NSLog(@"this is crash, [__NSArrayM] check object == nil (insertObject:atIndex:)") ;
return ;
}
[self safeInsertObject:object atIndex:index];
}
- (void)safeAddObject:(id)object {
if (object == nil) {
NSLog(@"this is crash, [__NSArrayM] check index (addObject:)") ;
return ;
}
[self safeAddObject:object];
}
- (id)emptyObjectIndex:(NSInteger)index {
NSLog(@"this is crash, [__NSArray0] check index (objectAtIndex:)") ;
return nil;
}
@end
复制代码
验证
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *arr1 = @[@"1",@"2"];
NSLog(@"[arr1 objectAtIndex:9527] = %@", [arr1 objectAtIndex:9527]);
NSLog(@"[arr1 objectAtIndexedSubscript:9527] = %@", [arr1 objectAtIndexedSubscript:9527]);
NSArray *arr2 = [[NSArray alloc]init];
NSLog(@"[arr2 objectAtIndex:9527] = %@", [arr1 objectAtIndex:9527]);
NSLog(@"[arr2 objectAtIndexedSubscript:9527] = %@", [arr1 objectAtIndexedSubscript:9527]);
NSArray *arr3 = [[NSArray alloc] initWithObjects:@"1",nil];
NSLog(@"[arr3 objectAtIndex:9527] = %@", [arr1 objectAtIndex:9527]);
NSLog(@"[arr3 objectAtIndexedSubscript:2] = %@", [arr3 objectAtIndexedSubscript:2]);
NSArray *arr4 = [NSArray alloc];
NSLog(@"[arr4 objectAtIndex:9527] = %@", [arr4 objectAtIndex:9527]);
NSLog(@"[arr4 objectAtIndexedSubscript:9527] = %@", [arr4 objectAtIndexedSubscript:9527]);
NSMutableArray *arr5 = [NSMutableArray array];
NSLog(@"[arr5 objectAtIndex:9527] = %@", [arr4 objectAtIndex:9527]);
NSLog(@"[arr5 objectAtIndexedSubscript:2] = %@", [arr5 objectAtIndexedSubscript:2]);
NSMutableArray *arr6 = [NSMutableArray array];
[arr6 addObject:nil];
[arr6 insertObject:nil atIndex:4];
[arr6 insertObject:@3 atIndex:4];
}
复制代码
Aspects是一个基于Method Swizzle
的iOS函数替换的第三方库,他能够很好的实现勾取一个类或者一个对象的某个方法,支持在方法执行前(AspectPositionBefore)
/执行后(AspectPositionAfter)
或替代原方法执行(AspectPositionInstead)
。
pod "Aspects"
复制代码
头文件
:#import <Aspects/Aspects.h>
复制代码
接口
声明以下:第一个:HOOK一个类的全部实例的指定方法
/// 为一个指定的类的某个方法执行前/替换/后,添加一段代码块.对这个类的全部对象都会起做用.
///
/// @param block 方法被添加钩子时,Aspectes会拷贝方法的签名信息.
/// 第一个参数将会是 `id<AspectInfo>`,余下的参数是此被调用的方法的参数.
/// 这些参数是可选的,并将被用于传递给block代码块对应位置的参数.
/// 你甚至使用一个没有任何参数或只有一个`id<AspectInfo>`参数的block代码块.
///
/// @注意 不支持给静态方法添加钩子.
/// @return 返回一个惟一值,用于取消此钩子.
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
复制代码
第二个:HOOK一个类实例的指定方法
/// 为一个指定的对象的某个方法执行前/替换/后,添加一段代码块.只做用于当前对象.
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
复制代码
options有以下选择:
AspectPositionAfter = 0, // 在原方法调用完成之后进行调用
AspectPositionInstead = 1, // 取代原方法
AspectPositionBefore = 2, // 在原方法调用前执行
AspectOptionAutomaticRemoval = 1 << 3 // 在调用了一次后清除(只能在对象方法中使用)
复制代码
参数
以下:// 一、被HOOK的元类、类或者实例
@property (nonatomic, unsafe_unretained, readonly) id instance;
// 二、方法参数列表
@property (nonatomic, strong, readonly) NSArray *arguments;
// 三、原来的方法
@property (nonatomic, strong, readonly) NSInvocation *originalInvocation;
// 执行原来的方法
[originalInvocation invoke];
复制代码
+(void)Aspect {
// 在类UIViewController全部的实例执行viewWillAppear:方法完毕后作一些事情
[UIViewController aspect_hookSelector:@selector(viewWillAppear:)
withOptions:AspectPositionAfter
usingBlock:^(id<AspectInfo> info) {
NSString *className = NSStringFromClass([[info instance] class]);
NSLog(@"%@", className);
} error:NULL];
// 在实例myVc执行viewWillAppear:方法完毕后作一些事情
UIViewController* myVc = [[UIViewController alloc] init];
[myVc aspect_hookSelector:@selector(viewWillAppear:)
withOptions:AspectPositionAfter
usingBlock:^(id<AspectInfo> info) {
id instance = info.instance; //调用的实例对象
id invocation = info.originalInvocation; //原始的方法
id arguments = info.arguments; //参数
[invocation invoke]; //原始的方法,再次调用
} error:NULL];
// HOOK类方法
Class metalClass = objc_getMetaClass(NSStringFromClass(UIViewController.class).UTF8String);
[metalClass aspect_hookSelector:@selector(ClassMethod)
withOptions:AspectPositionAfter
usingBlock:^(id<AspectInfo> info) {
NSLog(@"%@", HOOK类方法);
} error:NULL];
}
复制代码
注意:
Aspects
对类族无效,好比NSArray
须要使用系统方法对每一个子类单独hook
。- 全部的调用,都会是线程安全的。
Aspects
使用了Objective-C
的消息转发机会,会有必定的性能消耗.全部对于过于频繁的调用,不建议使用Aspects
。Aspects
更适用于视图/控制器相关的等每秒调用不超过1000次的代码。- 当应用于某个类时(使用类方法添加钩子),不能同时
hook
父类和子类的同一个方法;不然会引发循环调用问题.可是,当应用于某个类的示例时(使用实例方法添加钩子),不受此限制.- 使用KVO时,最好在
aspect_hookSelector:
调用以后添加观察者,不然可能会引发崩溃.
参考连接
Objc 黑科技 - Method Swizzle 的一些注意事项
iOS 如何实现Aspect Oriented Programming (上)