iOS Runtime
很多時候我們都在看iOS開發(fā)中的黑魔法——Runtime。懂很多,但如何實(shí)踐卻少有人提及。本文便是iOS Runtime的實(shí)踐第一篇。
WebView
我們這次的實(shí)踐主題,是使用針對接口編程的方式,借助Excalibur系統(tǒng),來達(dá)到動態(tài)切換UIWebView和WKWebkit的目的。
??為什么要動態(tài)切換?其實(shí)我們眾所周知,Apple的UIWebView存在巨大的內(nèi)存泄漏。當(dāng)網(wǎng)頁內(nèi)容較復(fù)雜,圖片較大時,經(jīng)常會出現(xiàn)150MB+的內(nèi)存占用率;并且這個內(nèi)存占用率會一直存在無法消除。雖然StackOverflow上有很多大神想出了各種方式,但作用卻很小。
??Apple 從 iOS8 開始,推出了更新、優(yōu)化更好的WKWebkit。這個庫是UIWebView的繼承者,在相同的瀏覽頁面下,WKWebKit提供的WKWebView的內(nèi)存占用率甚至可以只有UIWebView的1/10。可惜的是,我們很多時候?yàn)榱吮WC用戶的覆蓋率,target iOS Version都是 iOS7。這時候我們就需要使用UIWebView來達(dá)到顯示的目的。
那么問題來了,如何實(shí)現(xiàn)根據(jù)iOS版本來達(dá)到動態(tài)加載的目的呢?
Excalibur
Excalibur是我們用來映射類和字符串scheme對應(yīng)關(guān)系的類。通過注冊scheme對應(yīng)的類,來達(dá)到目的。
??注冊一個類:
+ (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時期才確定。
針對接口編程
在設(shè)計(jì)模式上,我們經(jīng)常聽到說,要針對接口編程。那么在iOS開發(fā)中,怎樣才算是針對接口編程呢?這個又有什么好處呢?
??在Objective-C語言中,我們一般認(rèn)為Protocol便是接口功能的協(xié)議。
??這里,我們想達(dá)到的目的,是在不同的iOS版本下,調(diào)用不同的Webkit來進(jìn)行網(wǎng)頁渲染。而網(wǎng)頁的渲染一般放在一個ViewController下,因此我們可以針對這個需求,制訂一個用來渲染指定URL的ViewController接口:
@protocol DWKProtocol <NSObject>
+ (instancetype)webViewControllerForUrl:(NSURL *)url;
@end
這里的接口,返回一個ViewController,該VC可以用來打開url網(wǎng)頁。
??現(xiàn)在我們可以寫兩個ViewController,分別是DWKWebViewController和DWKWebkitViewController;其中DWKWebViewController使用UIWebView來渲染網(wǎng)頁:
@interface DWKWebViewController ()
@property (nonatomic, strong) UIWebView *webView;
@property (nonatomic, strong) NSURL *url;
@end
而 DWKWebkitViewController則使用WKWebView來渲染網(wǎng)頁:
@interface DWKWebkitViewController ()
@property (nonatomic, strong) WKWebView *webView;
@property (nonatomic, strong) NSURL *url;
@end
接下來,二者在Runtime的初始化階段向Excalibur注冊自己:
DWKWebViewController
+ (void)load {
if (iOSVersion < 8.0) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[Excalibur registerScheme:DWK_MODULE_WEB_VC forClass:[self class]];
});
}
}
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模塊的實(shí)現(xiàn)者;而在8.0及其以上時,DWKWebkitViewController則是DWK_MODULE_WEB_VC模塊的實(shí)現(xiàn)者。
調(diào)用
做好了以上兩步準(zhǔn)備,接下來便是調(diào)用DWK_MODULE_WEB_VC的模塊來渲染網(wǎng)頁了。
這里,我們已經(jīng)約定好,實(shí)現(xiàn)DWK_MODULE_WEB_VC的ViewController肯定會實(shí)現(xiàn)DWKProtocol,因此我們可以這樣來獲取我們想要的ViewController Class:
Class <DWKProtocol> webViewControllerClass = [Excalibur classForScheme:DWK_MODULE_WEB_VC];
UIViewController<DWKProtocol> *webViewController = [webViewControllerClass webViewControllerForUrl:[NSURL URLWithString:@"www.baidu.com"]];
總結(jié)
至此,使用Runtime達(dá)到動態(tài)加載UIWebView和WKWebkit的目的達(dá)成。
代碼鏈接
我把代碼放到了Github上,希望對你有所幫助:
https://github.com/DemoMania/dynamicWebkit
如果有問題,還請留言。