iOS Runtime
很多時候我們都在看iOS開發中的黑魔法——Runtime。懂很多,但如何實踐卻少有人提及。本文便是iOS Runtime的實踐第一篇。
WebView
我們這次的實踐主題,是使用針對介面程式設計的方式,藉助Excalibur
系統,來達到動態切換UIWebView
和WKWebkit
的目的。
為什麼要動態切換?其實我們眾所周知,Apple的UIWebView
存在巨大的記憶體洩漏。當網頁內容較複雜,圖片較大時,經常會出現150MB+的記憶體佔用率;並且這個記憶體佔用率會一直存在無法消除。雖然StackOverflow上有很多大神想出了各種方式,但作用卻很小。
Apple 從 iOS8
開始,推出了更新、優化更好的WKWebkit
。這個庫是UIWebView
的繼承者,在相同的瀏覽頁面下,WKWebKit
提供的WKWebView
的記憶體佔用率甚至可以只有UIWebView
的1/10
。可惜的是,我們很多時候為了保證使用者的覆蓋率,target iOS Version
都是 iOS7
。這時候我們就需要使用UIWebView
來達到顯示的目的。
那麼問題來了,如何實現根據iOS
版本來達到動態載入的目的呢?
Excalibur
Excalibur
是我們用來對映類
和字串scheme
對應關係的類。通過註冊scheme
對應的類,來達到目的。
註冊一個類:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
+ (void)registerScheme:(nonnull NSString *)scheme forClass:(nonnull __unsafe_unretained Class)aClass { NSParameterAssert(scheme); NSParameterAssert(aClass); if ([Excalibur classForScheme:scheme]) { [NSException raise:@"Scheme Already Exists" format:@"'%@' Scheme Already Exists", scheme]; return; } if (![aClass isSubclassOfClass:[NSObject class]]) { [NSException raise:@"Wrong Class Type" format:@"Class should inherit from NSObject"]; return; } if ([scheme isEqualToString:@""]) { [NSException raise:@"Scheme Wrong" format:@"Scheme should not be blank"]; return; } [sharedInstance addScheme:scheme forClass:aClass]; } |
從Excalibur
中獲取scheme
指定的類
:
1 2 3 |
+ (nullable __unsafe_unretained Class)classForScheme:(nonnull NSString *)scheme { return [sharedInstance.mapTable objectForKey:scheme]; } |
通過Excalibur
,我們使用哪個類
,就可以在Runtime
時期才確定。
針對介面程式設計
在設計模式上,我們經常聽到說,要針對介面程式設計
。那麼在iOS開發中,怎樣才算是針對介面程式設計
呢?這個又有什麼好處呢?
在Objective-C
語言中,我們一般認為Protocol
便是介面
功能的協議。
這裡,我們想達到的目的,是在不同的iOS
版本下,呼叫不同的Webkit
來進行網頁渲染。而網頁的渲染一般放在一個ViewController
下,因此我們可以針對這個需求,制訂一個用來渲染指定URL
的ViewController
介面:
1 2 3 4 5 |
@protocol DWKProtocol + (instancetype)webViewControllerForUrl:(NSURL *)url; @end |
這裡的介面,返回一個ViewController
,該VC
可以用來開啟url
網頁。
現在我們可以寫兩個ViewController
,分別是DWKWebViewController
和DWKWebkitViewController
;其中DWKWebViewController
使用UIWebView
來渲染網頁:
1 2 3 4 5 6 |
@interface DWKWebViewController () @property (nonatomic, strong) UIWebView *webView; @property (nonatomic, strong) NSURL *url; @end |
而 DWKWebkitViewController
則使用WKWebView
來渲染網頁:
1 2 3 4 5 6 |
@interface DWKWebkitViewController () @property (nonatomic, strong) WKWebView *webView; @property (nonatomic, strong) NSURL *url; @end |
接下來,二者在Runtime
的初始化階段向Excalibur
註冊自己:
DWKWebViewController
1 2 |
+ (void)load { if (iOSVersion |
DWKWebkitViewController
1 2 3 4 5 6 7 8 |
+ (void)load { if (iOSVersion >= 8.0) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [Excalibur registerScheme:DWK_MODULE_WEB_VC forClass:[self class]]; }); } } |
那麼,8.0以下時,DWKWebViewController
就會是DWK_MODULE_WEB_VC
模組的實現者;而在8.0及其以上時,DWKWebkitViewController
則是DWK_MODULE_WEB_VC
模組的實現者。
呼叫
做好了以上兩步準備,接下來便是呼叫DWK_MODULE_WEB_VC
的模組來渲染網頁了。
這裡,我們已經約定好,實現DWK_MODULE_WEB_VC
的ViewController
肯定會實現DWKProtocol
,因此我們可以這樣來獲取我們想要的ViewController Class
:
1 2 |
Class webViewControllerClass = [Excalibur classForScheme:DWK_MODULE_WEB_VC]; UIViewController *webViewController = [webViewControllerClass webViewControllerForUrl:[NSURL URLWithString:@"www.baidu.com"]]; |
總結
至此,使用Runtime
達到動態載入UIWebView
和WKWebkit
的目的達成。
程式碼連結
我把程式碼放到了Github
上,希望對你有所幫助:
1 |
https://github.com/DemoMania/dynamicWebkit |
如果有問題,還請留言。