不少時候咱們都在看iOS開發中的黑魔法——Runtime。懂不少,但如何實踐卻少有人說起。本文即是iOS Runtime的實踐第一篇。git
WebViewgithub
咱們此次的實踐主題,是使用針對接口編程的方式,藉助Excalibur系統,來達到動態切換UIWebView和WKWebkit的目的。web
爲何要動態切換?其實咱們衆所周知,Apple的UIWebView存在巨大的內存泄漏。當網頁內容較複雜,圖片較大時,常常會出現150MB+的內存佔用率;而且這個內存佔用率會一直存在沒法消除。雖然StackOverflow上有不少大神想出了各類方式,但做用卻很小。編程
Apple 從 iOS8 開始,推出了更新、優化更好的WKWebkit。這個庫是UIWebView的繼承者,在相同的瀏覽頁面下,WKWebKit提供的WKWebView的內存佔用率甚至能夠只有UIWebView的1/10。惋惜的是,咱們不少時候爲了保證用戶的覆蓋率,target iOS Version都是 iOS7。這時候咱們就須要使用UIWebView來達到顯示的目的。設計模式
那麼問題來了,如何實現根據iOS版原本達到動態加載的目的呢?優化
Excaliburatom
Excalibur是咱們用來映射類和字符串scheme對應關係的類。經過註冊scheme對應的類,來達到目的。url
註冊一個類:spa
+ (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指定的類:
+ (nullable __unsafe_unretained Class)classForScheme:(nonnull NSString *)scheme {
return [sharedInstance.mapTable objectForKey:scheme];
}
經過Excalibur,咱們使用哪一個類,就能夠在Runtime時期才肯定。
針對接口編程
在設計模式上,咱們常常聽到說,要針對接口編程。那麼在iOS開發中,怎樣纔算是針對接口編程呢?這個又有什麼好處呢?
在Objective-C語言中,咱們通常認爲Protocol即是接口功能的協議。
這裏,咱們想達到的目的,是在不一樣的iOS版本下,調用不一樣的Webkit來進行網頁渲染。而網頁的渲染通常放在一個ViewController下,所以咱們能夠針對這個需求,制訂一個用來渲染指定URL的ViewController接口:
@protocol DWKProtocol
+ (instancetype)webViewControllerForUrl:(NSURL *)url;
@end
這裏的接口,返回一個ViewController,該VC能夠用來打開url網頁。
如今咱們能夠寫兩個ViewController,分別是DWKWebViewController和DWKWebkitViewController;其中DWKWebViewController使用UIWebView來渲染網頁:
@interface DWKWebViewController ()
@property (nonatomic, strong) UIWebView *webView;
@property (nonatomic, strong) NSURL *url;
@end
而 DWKWebkitViewController則使用WKWebView來渲染網頁:
@interface DWKWebkitViewController ()
@property (nonatomic, strong) WKWebView *webView;
@property (nonatomic, strong) NSURL *url;
@end
接下來,兩者在Runtime的初始化階段向Excalibur註冊本身:
DWKWebViewController
+ (void)load {
if (iOSVersion
DWKWebkitViewController
+ (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:
Class webViewControllerClass = [Excalibur classForScheme:DWK_MODULE_WEB_VC];
UIViewController *webViewController = [webViewControllerClass webViewControllerForUrl:[NSURL URLWithString:@"www.baidu.com"]];
總結
至此,使用Runtime達到動態加載UIWebView和WKWebkit的目的達成。
代碼連接
我把代碼放到了Github上,但願對你有所幫助:
https://github.com/DemoMania/dynamicWebkit