面向切面編程參考: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 (上)