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
对应的类,来达到目的。
注册一个类:
+ (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 <NSObject>
+ (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 < 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
模块的实现者;而在8.0及其以上时,DWKWebkitViewController
则是DWK_MODULE_WEB_VC
模块的实现者。
调用
做好了以上两步准备,接下来便是调用DWK_MODULE_WEB_VC
的模块来渲染网页了。
这里,我们已经约定好,实现DWK_MODULE_WEB_VC
的ViewController
肯定会实现DWKProtocol
,因此我们可以这样来获取我们想要的ViewController Class
:
Class <DWKProtocol> webViewControllerClass = [Excalibur classForScheme:DWK_MODULE_WEB_VC];
UIViewController<DWKProtocol> *webViewController = [webViewControllerClass webViewControllerForUrl:[NSURL URLWithString:@"www.baidu.com"]];
总结
至此,使用Runtime
达到动态加载UIWebView
和WKWebkit
的目的达成。
代码链接
我把代码放到了Github
上,希望对你有所帮助:
https://github.com/DemoMania/dynamicWebkit
如果有问题,还请留言。
网友评论
1.哪里应用到了runtime呢?
2.您本文所达到的目的是:根据系统版本来实现webView的切换,而且,最后实际上是根据系统版本做切换的。那么,这种根本不会动态切换的因素,怎么会涉及到动态切换呢?所谓的映射,为什么不直接用您最“内核”的版本判断来做判断去加载就好了呢?
#import <objc/runtime.h>
#import <objc/message.h>
注释掉还是可以运行