一丶介紹
Aspect Oriented Programming(AOP),面向切面編程
AOP主要實現的目的是針對業務處理過程中的切面進行提取,它所面對的是處理過程中的某個步驟或階段,以獲得邏輯過程中各部分之間低耦合性的隔離效果。
[1] 比如我們最常見的就是日志記錄了,舉個例子,我們現在提供一個服務查詢學生信息的,但是我們希望記錄有誰進行了這個查詢。如果按照傳統的OOP的實現的話,那我們實現了一個查詢學生信息的服務接口(StudentInfoService)和其實現類(StudentInfoServiceImpl.java),同時為了要進行記錄的話,那我們在實現類(StudentInfoServiceImpl.java)中要添加其實現記錄的過程。這樣的話,假如我們要實現的服務有多個呢?那就要在每個實現的類都添加這些記錄過程。這樣做的話就會有點繁瑣,而且每個實現類都與記錄服務日志的行為緊耦合,違反了面向對象的規則。那么怎樣才能把記錄服務的行為與業務處理過程中分離出來呢?看起來好像就是查詢學生的服務自己在進行,但卻是背后日志記錄對這些行為進行記錄,并且查詢學生的服務不知道存在這些記錄過程,這就是我們要討論AOP的目的所在。AOP的編程,好像就是把我們在某個方面的功能提出來與一批對象進行隔離,這樣與一批對象之間降低了耦合性,可以就某個功能進行編程。
---摘自百度百科
我們要用AOP來做什么呢?
1.搭建Control的時候,一般會寫個BaseViewController,然后把相同功能的代碼放在相同的函數內比如以下:
- (void)viewDidLoad
{
[super viewDidLoad];
//創建視圖代碼
[self createUI];
//初始化數據
[self initdata];
//網絡請求
[self askNetwork];
}
規范代碼,可以減少不同開發者之間溝通成本,以及提高問題的定位速度,減少解決時間;
鑒于BaseViewController太臃腫,有了aop編程,就有了新的解決方案;
二丶Aspects的介紹
OC 有一個成熟的aspect方案->Aspects
地址:https://github.com/steipete/Aspects
源碼解析及應用:http://wereadteam.github.io/2016/06/30/Aspects/
主要還是用到oc神奇的runtime機制,動態的改變了 selector 和 IMP 的對應關系;(此圖并非原創)
主要用到2個方法:
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
/// Adds a block of code before/instead/after the current `selector` for a specific instance.
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
block 執行的時機
typedef NS_OPTIONS(NSUInteger, AspectOptions) {
AspectPositionAfter = 0, /// Called after the original implementation (default)
AspectPositionInstead = 1, /// Will replace the original implementation.
AspectPositionBefore = 2, /// Called before the original implementation.
AspectOptionAutomaticRemoval = 1 << 3 /// Will remove the hook after the first execution.
};
三丶Aspect OC應用
@implementation UIViewController (Base)
+ (void)load
{
NSError *error = nil;
[self aspect_hookSelector:@selector(viewDidLoad) withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo> aspectInfo){
UIViewController *baseVc = [aspectInfo instance];
[baseVc createUI];
[baseVc initdata];
[baseVc askNetwork];
}error:&error];
if (error)
{
Log(@"Load error: %@",error);
}
}
@end
這么寫成Categorys其實不用BaseViewcontrol也是可以的;只要導入#import "UIViewController+Base.h" 文件就可以;優化不是一點點;
四丶Swift 實現
首先要先知道幾個東西;
1.swift 沒有load方法,使用initialize()
2.@convention關鍵字的作用:
2.1 修飾 Swift 中的函數類型,調用 C 的函數時候,可以傳入修飾過 @convention(c) 的函數類型,匹配 C 函數參數中的函數指針。
2.2 修飾 Swift 中的函數類型,調用 Objective-C 的方法時候,可以傳入修飾過 @convention(block) 的函數類型,匹配 Objective-C 方法參數中的 block 參數
3.unsafeBitCast
unsafeBitCast是非常危險的操作,它會將一個指針指向的內存強制按位轉換為目標的類型。因為這種轉換是在Swift的類型管理之外進行的,因此編譯器無法確保得到的類型是否確實正確,你必須明確地知道你在做什么
4.需要把原先填寫block的參數,轉成AnyObject
具體代碼:
override public class func initialize() {
/*
@convention
1. 修飾 Swift 中的函數類型,調用 C 的函數時候,可以傳入修飾過 @convention(c) 的函數類型,匹配 C 函數參數中的函數指針。
2. 修飾 Swift 中的函數類型,調用 Objective-C 的方法時候,可以傳入修飾過 @convention(block) 的函數類型,匹配 Objective-C 方法參數中的 block 參數
*/
let block: @convention(block) (AnyObject!) -> Void = {
info in
let aspectInfo = info as! AspectInfo
let control = aspectInfo.instance()
#需要判類,
if let myVc = control as? BaseViewController{
myVc.customView()
myVc.createUI()
myVc.askNetwork()
}
}
#block轉AnyObject
let blobj: AnyObject = unsafeBitCast(block, to: AnyObject.self)
do {
let originalSelector = NSSelectorFromString("viewDidLoad")
#在viewDidLoad之后調用
try UIViewController.aspect_hook(originalSelector, with: .positionBefore, usingBlock: blobj)
} catch {
print("error = \(error)")
}
}