版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2017.07.27 |
前言
OC是運行時的語言,底層就是運行時,可以說runtime是OC的底層,很多事情也都可以用運行時解決,下面就講述一下運行時runtime的知識以及它的妙用。感興趣的可以看上面幾篇。
1. 運行時runtime深度解析(一)—— API
Method Swizzling
Method Swizzing
是發生在運行時的,主要用于在運行時將兩個Method
進行交換,我們可以將Method Swizzling
代碼寫到任何地方,但是只有在這段Method Swilzzling
代碼執行完畢之后互換才起作用。而且Method Swizzling
也是iOS中AOP
(面相切面編程)的一種實現方式,我們可以利用蘋果這一特性來實現AOP
編程。
在OC語言的runtime特性中,調用一個對象的方法就是給這個對象發送消息。是通過查找接收消息對象的方法列表,從方法列表中查找對應的SEL,這個SEL對應著一個IMP(一個IMP可以對應多個SEL),通過這個IMP找到對應的方法調用。在每個類中都有一個Dispatch Table
,這個Dispatch Table本質是將類中的SEL和IMP(可以理解為函數指針)進行對應。而我們的Method Swizzling就是對這個table進行了操作,讓SEL對應另一個IMP。
下面看其原理圖。
1. 方法互換在頁面統計上的應用需求
很多公司都有頁面統計這個需求,這里我們也做一下統計,主要有兩種思路:
- 在每一個控制器中
viewDidLoad
方法中統計用戶進入控制器的次數,并上報至服務器。但是這有個缺點就是每一個控制器都要加很是繁瑣。 - 還有一種辦法就是寫一個
UIViewController
的分類Category
,然后在Category
中的+(void)load
方法中添加Method Swizzling
方法,我們用來替換的方法也寫在這個Category中。由于load類方法是程序運行時這個類被加載到內存中就調用的一個方法,執行比較早,并且不需要我們手動調用。而且這個方法具有唯一性,也就是只會被調用一次,不用擔心資源搶奪的問題。
這里我們采用的是第二種方法,正好也驗證下Method Swizzling
方法的使用。
2. 方法互換在頁面統計上的應用實現
下面我們就直接看代碼吧。
1. JJRuntimeVC.h
#import <UIKit/UIKit.h>
@interface JJRuntimeVC : UIViewController
@end
2. JJRuntimeVC.m
#import "JJRuntimeVC.h"
#import "UIViewController+JJSwizzlingCategory.h"
@interface JJRuntimeVC ()
@end
@implementation JJRuntimeVC
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor greenColor];
}
@end
3. UIViewController+JJSwizzlingCategory.h
#import <UIKit/UIKit.h>
@interface UIViewController (JJSwizzlingCategory)
@end
4. UIViewController+JJSwizzlingCategory.m
#import "UIViewController+JJSwizzlingCategory.h"
#import <objc/runtime.h>
@implementation UIViewController (JJSwizzlingCategory)
#pragma mark - Override Base Function
+ (void)load
{
[super load];
//通過class_getInstanceMethod()函數從當前對象中的method list獲取method結構體,如果是類方法就使用class_getClassMethod()函數獲取。
Method replacedMathod = class_getInstanceMethod([self class], @selector(viewDidLoad));
Method toReplaceMethod = class_getInstanceMethod([self class], @selector(swizzlingMethodViewDidLoad));
// 我們在這里使用class_addMethod()函數對Method Swizzling做了一層驗證,如果self沒有實現被交換的方法,會導致失敗。
// 而且self沒有交換的方法實現,但是父類有這個方法,這樣就會調用父類的方法,結果就不是我們想要的結果了。
// 所以我們在這里通過class_addMethod()的驗證,如果self實現了這個方法,class_addMethod()函數將會返回NO,我們就可以對其進行交換了。
if (!class_addMethod([self class], @selector(viewDidLoad), method_getImplementation(toReplaceMethod), method_getTypeEncoding(toReplaceMethod))) {
method_exchangeImplementations(replacedMathod, toReplaceMethod);
}
}
#pragma mark - Action && Notification
- (void)swizzlingMethodViewDidLoad
{
NSString *str = [NSString stringWithFormat:@"%@",self.class];
// 我們在這里加一個判斷,將系統的UIViewController的對象剔除掉
if (![str containsString:@"UI"]) {
NSLog(@"統計打點:%@",self.class);
}
[self swizzlingMethodViewDidLoad];
}
@end
運行代碼會發現,先走+ (void)load
實現方法的互換,再走控制器JJRuntimeVC
中的viewDidLoad
方法,但是由于在+ (void)load
中對方法的實現做了互換,所以走的是方法- (void)swizzlingMethodViewDidLoad
,在這個方法內部接著調用[self swizzlingMethodViewDidLoad];
,同樣由于方法實現的互換,其實調用的是方法- (void)viewDidLoad
,所以最后又走正常的走了這個方法,同時實現了對加載控制器的統計,下面看結果輸出。
2017-07-27 19:07:27.892845+0800 JJOC[5763:1911605] 統計打點:JJRuntimeVC
這樣就利用運行時實現了頁面統計。
參考文獻
1. Method Swizzling
2. Objective-C Runtime 運行時之四:Method Swizzling
3. iOS黑魔法-Method Swizzling
后記
未完,待續~~~~