iOS Runtime 实践(1)

作者: 圣迪 | 来源:发表于2016-01-23 21:26 被阅读3353次

    iOS Runtime

    很多时候我们都在看iOS开发中的黑魔法——Runtime。懂很多,但如何实践却少有人提及。本文便是iOS Runtime的实践第一篇。

    WebView

    我们这次的实践主题,是使用针对接口编程的方式,借助Excalibur系统,来达到动态切换UIWebViewWKWebkit的目的。
      为什么要动态切换?其实我们众所周知,Apple的UIWebView存在巨大的内存泄漏。当网页内容较复杂,图片较大时,经常会出现150MB+的内存占用率;并且这个内存占用率会一直存在无法消除。虽然StackOverflow上有很多大神想出了各种方式,但作用却很小。
      Apple 从 iOS8 开始,推出了更新、优化更好的WKWebkit。这个库是UIWebView的继承者,在相同的浏览页面下,WKWebKit提供的WKWebView的内存占用率甚至可以只有UIWebView1/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下,因此我们可以针对这个需求,制订一个用来渲染指定URLViewController接口:

    @protocol DWKProtocol <NSObject>
    
    + (instancetype)webViewControllerForUrl:(NSURL *)url;
    
    @end
    

    这里的接口,返回一个ViewController,该VC可以用来打开url网页。
      现在我们可以写两个ViewController,分别是DWKWebViewControllerDWKWebkitViewController;其中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_VCViewController肯定会实现DWKProtocol,因此我们可以这样来获取我们想要的ViewController Class

    Class <DWKProtocol> webViewControllerClass = [Excalibur classForScheme:DWK_MODULE_WEB_VC];
    UIViewController<DWKProtocol> *webViewController = [webViewControllerClass webViewControllerForUrl:[NSURL URLWithString:@"www.baidu.com"]];
    

    总结

    至此,使用Runtime达到动态加载UIWebViewWKWebkit的目的达成。

    代码链接

    我把代码放到了Github上,希望对你有所帮助:

    https://github.com/DemoMania/dynamicWebkit
    

    如果有问题,还请留言。

    相关文章

      网友评论

      • XTShow:拜读了您的文章和github的demo,有几个问题想请教您一下:
        1.哪里应用到了runtime呢?
        2.您本文所达到的目的是:根据系统版本来实现webView的切换,而且,最后实际上是根据系统版本做切换的。那么,这种根本不会动态切换的因素,怎么会涉及到动态切换呢?所谓的映射,为什么不直接用您最“内核”的版本判断来做判断去加载就好了呢?
      • Assuner:前来膜拜,学习!:relieved:
      • 伦敦乡下的小作家:小弟学习了
      • 念奴娇如画:不明觉厉啊。。
      • a05996b9ecd0:膜拜 :blush:
      • 6cc787a7dc9c:受益很多 谢谢
      • 蚂蚁_a:用到runtime的哪个语法?
        #import <objc/runtime.h>
        #import <objc/message.h>
        注释掉还是可以运行
      • dd5c1dd5d996:我想问一下,你们使用webkit,有解决cookies的问题么,webkit的cookies和webview的cookies不是同一套
      • 超_iOS:不明觉厉有没有基础点的
      • A天天涨不停:大神,写的很好,很棒很赞。给你32赞。思密达

      本文标题:iOS Runtime 实践(1)

      本文链接:https://www.haomeiwen.com/subject/zcerkttx.html