美文网首页iOS开发技巧iOS开源&高仿项目精选iOS开发
从【简书】iOS客户端,来谈谈Hybrid方案细节设计

从【简书】iOS客户端,来谈谈Hybrid方案细节设计

作者: halohily | 来源:发表于2017-08-06 05:53 被阅读6566次

作为一位 iOS 开发人员,你应该已经敏感地发现,自己的工作涉及内容已经不止于 Native 的部分,因为 Hybrid App 和 ReactNative 等技术方案已经不仅仅是概念,越来越多的公司开始着手自己的 Hybrid 方案以及 ReactNative 本地化工作。

一、引言

介绍相关概念的优秀文章已经有许多,方案的实现原理你也应该已经或多或少有了一些理解。不了解也没有关系,在这篇文章里,我将用简书 iOS 客户端的有关特性,来探索一下 Hybrid 方案的技术细节。文章的目的是抛砖引玉,用一个具体的项目,大家很熟悉的简书客户端,来帮助大家认识 Hybrid 方案,然后亲自实现它

从现在开始,不再着眼于某一个 feature ,你需要站在一个客户端架构师的角度来看待问题。

二、我们用到的简书客户端特性

1.界面构成分析
  • 本文的主角是简书 iOS 客户端的文章展示页面,这是我的一篇文章的展示页面: 文章展示页面
  • 如你所见,文章内容的展示是使用webview控件,具体是UIWebview还是WKWebview按下不表,这不是本文的关键。在我的demo中,我使用了UIWebview
  • 在简书发现tab栏的内容顶部,还有一个热门内容推荐的轮播图。与它类似是一些app内的活动推介轮播图,以及广告页面,它们的详情页内容展示多使用webview。在简书中,这个轮播图对应的下一级页面也是文章展示页面,特性基本一致。
    热门内容推荐轮播图
  • 在 webview 的基础上,添加了符合浏览器用户习惯的导航栏按钮。包括左侧的返回关闭按钮。以及右侧的功能列表按钮。
  • 页面底部,是一个工具栏,提供了四个常用的操作。注意这里的评论按钮,它是我们下文的一个谈论点。
2.界面特性分析
  • 一般各家客户端的内容页,都会有一些适于自己功能点的设计。简书也不例外。比如,在文章内容区域点击作者的头像(它本身也是网页的一部分,暂且理解为对应一个链接),跳转到了作者的个人主页,注意,容易发现它是一个客户端的原生页面,也就是一个VC。


    作者个人主页
  • 点击底部工具栏中的评论按钮(原生组件),页面(web页面)会滑动到评论区域,如图
    点击评论按钮,页面滑动
  • 对一篇文章写下自己的评论(使用了原生组件),评论列表(网页内容)进行更新。
  • 简书对于展示内容作了内外站的区分。据我自己的简要测试,来自简书域名www.jianshu.com下的内容,在加载过程中,是没有进度条的,用户体验非常接近原生页面。而第三方的内容,则在加载过程中会出现一般浏览器中常见的加载进度条,如图:
    第三方内容的加载进度条
  • 对于简书域名下的内容,不会出现叉号的关闭按钮,这也是为了营造接近原生页面的用户体验,让用户不会察觉到这是一个 web 界面。而第三方内容,则会出现符合浏览器使用习惯的关闭按钮,如上图。

三、我们需要的储备知识

1.Hybrid相关
  • 在Hybrid架构中,原生界面和web页面需要频繁地沟通,并且是双向的沟通。原生代码可以构建JavaScript语句,交由webview进行执行,从而在web页面上实现需要的效果。而在web页面的js文件中,也可以调用原生的Objective-C方法,从而执行一些原生方法才能完成的操作。与此相关的库有WebViewJavascriptBridge以及JavaScriptCore,有需要的同学可以自行了解。
2.UIWebview的相关特性
  • UIWebviewDelegate
    webview的代理方法大家想必非常熟悉,我们可以在页面加载前、开始加载时、加载完成时以及失败时进行需要的操作。这里我们需要用到的是这一条代理方法:
    - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
    webview根据它的返回结果来决定是否进行加载。
  • 执行JS语句的方法:
    - (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
    我们可以自行构建一条JS语句,通过这个方法交由webview执行
  • goback相关
    UIWebview拥有布尔类型的canGoBackloading等属性,通过监测它们的值我们可以知道当前页面是否可以进行回退,以及页面是否正在加载。
    与之对应,拥有- (void)goBack;等方法,调用之页面会进行返回,就像我们在浏览器中常见的那样。

四、相关特性的模仿实现

对于上文中提到的相关特性,我写了一个demo,对它们进行了简要的模仿实现。当然简书官方的实现会考虑到方方面面,而我的demo仅是从Hybrid架构的思想出发,盼能够抛砖引玉。
这是demo中对该页面的模仿实现:

demo页面
1.页面初始化

在demo中,使用一条web页面的URL来初始化VC:- (instancetype)initWithURL:(NSURL *)URL;这条URL对应文章的链接。
顶部导航栏和底部工具栏都是系统原生的UINavigationBarUIToolBar,按钮素材使用阿里巴巴的iconfont字体。

2.点击作者头像进入个人主页

关于这个特性的实现,如果按照 Hybrid 架构的思想,属于 Web 页面调用原生方法,进入一个原生的VC。点击头像,JS脚本执行相关代码,调用原生方法暴露出来的接口,执行原生方法。
我在这里用一种简要的方法实现:原生代码利用之前提到的代理方法,在用户点击头像后,拦截该URL,分析URL为头像部分,直接执行原生方法跳转到个人主页VC。
通过分析简书文章页面的网页源代码,我发现用户头像对应的URL中的Query部分,有一个参数为utm_medium=note-author-link。据此,在- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType代理方法中加以判断,若是头像链接,则跳转到个人主页VC。下面是相关代码:

NSURL *destinationURL = request.URL;
    NSString *URLQuery = destinationURL.query;
//    简书点击文章中头像时跳转至原生页面。此处利用头像链接中的一个参数作判断
    if ([URLQuery containsString:@"utm_medium=note-author-link"])
    {
        NSLog(@"我跳转到个人主页啦");
        AvatorViewController *avatorVC = [[AvatorViewController alloc] init];
        [self.navigationController pushViewController:avatorVC animated:YES];
        return NO;
    }

最后返回NO是因为若是头像链接,该web页面是不需要做跳转操作的。

这里顺便讲一个小tips:如果想要在Mac端查看移动端的网页源代码,那么你只需要在Safari中输入该页面,并且在开发选项下的用户代理中,选择iOS系统下的Safari作为代理,这时再使用源代码查看,看到的就是移动端的网页源代码了。

3.点击评论按钮,页面滑动到评论区域

这个特性的实现方式和上面类似,点击评论按钮,原生代码构建一条JS语句,交由- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;方法进行执行,由web页面执行滑动操作。代码如下:

- (void)scrollToCommentField
{
    [self stringByEvaluatingJavaScriptFromString:@"scrollTo(0,20500)"];
}

这里的JS语句非常简单,由于笔者的前端知识还有所欠缺,没有想到可以精确滑动到评论区域的JS语句,所以简要实现,点到为止。

4.原生组件写评论,web页面更新

这里首先需要贴一下文章页面的网页源代码:

<!-- 评论列表 -->
  <div data-vcomp="comments-list" data-lazy="3">
    <script type="application/json">
      {"likedNote":true,"commentable":true,"publicCommentsCount":3,"noteId":2491941,"likesCount":43}
    </script>
  </div>

可以看到,页面的评论内容是异步加载的。所以这个功能的实现,我
认为比较合理的逻辑是原生组件向服务器提交一条新的评论,收到成功回调之后,原生组件和web页面进行交互,执行更新并加载评论列表的JS代码,从而看到自己发的新评论。

5.内外站页面的区分

这里和点击头像的实现方法类似,通过拦截链接的URL,区分内部链接和第三方链接,从而在开始加载的时候采用不同的加载界面,或者对于第三方链接单独开启一个第三方VC。
demo中关于第三方链接的关闭按钮的显示逻辑,做出了相应的处理。

五、demo中的不足

看到这里大家应该就会发现,对于提到Hybrid我们就会想到的Bridge、Router等模块,我并没有做明显的限定。这样也是为了方便大家用一种更接近以往原生代码编写的思维,来理解Hybrid模式。
同时,demo中较多涉及了原生代码对web页面做出的沟通操作。而没有JS代码对原生代码的调用,这是因为一来站在一个简书客户端的用户和iOS开发的角度,对于JS端执行的操作,有些力不能及,这本是和你共同工作的前端伙伴的任务,二来对于一篇帮助大家入门Hybrid的文章来说,从这个单方面的交互来入手,管中窥豹,已是足够。

六、一些感悟

其实,写了这么多,我觉得收获到一些感悟是最重要的,下面的要讲的,可能是我觉得更为重要的思想性的东西。

1.未来的趋势之一,便是大前端团队进行客户端开发。
  • 看到这里你发现,如果你们的团队想要采用Hybrid模式进行产品的开发,光靠iOS或者是安卓的客户端工程师是不可能完成的。在客户端框架的开发过程中,需要和前端的工程师沟通具体的技术细节。比如怎样设计接口能够更好地兼顾客户端和前端特点,对于某个问题,如何能把握全局而不是单单从客户端的角度来看待。这些可能是普通的iOS开发工程师和大牛的差距所在之一。
  • 越来越多的客户端工程师招聘要求中,出现了熟悉前端语言的要求。如果你能在精通客户端开发之余,对前端语言也游刃有余,那么在接下来的发展趋势中,就会有更多的可能性。所以,请开始你的前端学习吧~。
2.在Hybrid模式下,如何进行产品技术方案的取舍
  • 如前文所见,简书客户端对于内部域名的内容和第三方内容,在展示方式上是有明显不同的。在阅读简书的文章时,让用户发现不了自己是在一个浏览器上进行阅读,这在方方面面就极大改善了用户体验。为了做到这一点,我推测简书首先需要对自己的内容进行非常良好的CDN加速,以保证内容加载时不会耗时过长,同时采取一些预加载策略,二是在内容加载时,采用与原生界面部分相同的loading界面,去掉进度条,模拟原生界面的加载过程。而对于第三方的链接,采用进度条+返回、关闭按钮的设计,则更符合用户在浏览器中进行阅读的习惯,也可以和自己的内容进行直观区分,这也改善了用户体验。
  • 对于某些原生和Web页面都可以实现的特性如何取舍,这也是需要考虑的问题。比如,点击评论按钮页面滑动,这个功能使用web页面的滑动而非原生的控制显然更为自然也更符合用户习惯。而对于撰写评论的功能,使用原生的键盘、编辑器组件,当然就比使用web页面的键入更加稳定可控了。

七、文章的demo

对于文章内容,我写了一个demo,这是demo地址

为了便于理解,我为代码写了详尽的注释。
如果觉得它对你有帮助,不妨在github上为我点一个star~非常感谢!

相关文章

网友评论

  • 小代码仔:喜欢这样的文章,很多时候欠缺的都是一个思路。详细看了您的文章以及所有评论,不知道楼主所说的更好的“js对原生的调用”有没有新的研究。希望可以再做一些借鉴。
  • 蜿蜒花骨朵:用模拟器运行过程中,滑动界面时候,程序莫名崩溃。(多加载几次不同文章)
    halohily:@蜿蜒花骨朵 这个我也测试到过,加载别的页面的时候没有复现,应该是因为没有对简书的这个web页面做处理。其实简书肯定不会是把原本的页面直接加载的,这篇文章写完又有一些思考,简书的实现应该是从后端拿到html代码,然后本地渲染HTML代码。
  • kinmo:最近我也在做这个效果,思路是:文章内容用webview,评论部分用tableview,然后把tableview放到webview里面去。说实话效果一直不好,作者这文章倒是给了我不错的思路。
    halohily:@July丶ye webview不光可以loadURL,还可以loadHTML。也就是后端不是给你返回链接,而是直接返回html代码,你在客户端本地拿到html进行处理或者不处理,再用webview渲染出来,这样页面的很多信息,你在渲染之前都是可以知道的,可能对你说的“效果”有帮助
    kinmo:@halohily webview渲染怎么理解?
    halohily:@July丶ye 你可以先拿到html数据,然后webview渲染本地的html,这样效果会好点
  • 东篱先生_:ios7苹果提供了javascriptcore替代webview代理协议拦截的做原生与js的交互,更加灵活,更丝滑。随着reactnative的不断完善,坑也被填的差不多了,hybrid方案的跨平台,动态下发优势会逐渐被rn取代。
  • c7ff39568848:交互不是简单的url拦截
    halohily:@薇凤的腾 同意你的观点。不过这里仅仅是选了一个比较明显的特性,用了一种简单的实现,并不是说交互就止于此:wink:
  • ddaa8dae50b0:有几个错误的地方,简书app的评论列表是完全原生开发,精确控制方便。文章内容的加载,这块没有仔细看,不过应该是和头条一样,用原生代码加载和处理html保存在本地,最后才用webview渲染,即保留了网页的风格,又能体现原生的速度优势,webview加载图片实在是不友好,即便用了懒加载。
    halohily:@Ian_He 重点的是webview渲染出来的内容如何达到接近原生的体验效果,当然这是前端和客户端共同的工作。并且一种技术对应的场景没有有没有一说,而是需不需要。另外,进度条相关的内容,我是在举例web内容和原生交互的场景。当然,你可以说我举例的场景使用hybrid并不适合,我也认同,但这不代表使用这些场景来引出概念是“错误”的。最后,我在文章倒数第二个章节已经说了,我这篇文章并没有在追求hybrid内容的完全概括,更为重点的js对原生的调用我也没有涉及,这是我写写明了的。不知道你是否认同呢。最后,谢谢你的理性讨论:wink:,我也是学习者,欢迎前辈的观点:wink:
    ddaa8dae50b0:@halohily 我看了文章才能指出你推断错误的地方。hybrid的关键是webview的加载如何达到原生的效果,没有你文章中所说的延迟和进度条,最关键的没有讲到。
    halohily:@Ian_He 我觉得你可以先读一下文章的:wink:。这篇文章并不是在推断简书客户端的实现方案,我的重点在于简书客户端如果使用hybrid模式开发的话该怎么设计方案,我也已经说过了,简书完全有可能根本不是这么开发的,但对于这篇文章的立足点来说,毫无影响。
  • 妖妖零幺幺:请教一下demo中的图片怎么添加上的,一脸懵逼。。。在项目中没有找到图片,这是什么写法?
    UIView *rewardView = [LYIconfont getToolBarCustomViewWithCode:@"\U0000e855" Title:@"赞赏"];
    EnjoyJS:访问 iconfont.cn, 里面有图标转字体的详细方法, 可以试一试, 效果还不错
    halohily:@妖妖零幺幺 iconfont,具体你可以看下我这篇文章:http://www.jianshu.com/p/5fec21a164f3
    ducks:iconfont 转换
  • 4b4d42e9a1bb:比较好奇作者是间书的iOS开发吗
    4b4d42e9a1bb:@halohily 点个赞
    halohily:@吴章平 不是的呀,普通简书用户一枚:wink:。这些技术方案只是我对简书客户端的分析,也肯定会有疏漏之处,甚至简书完全不是这么开发的。只不过是想用大家比较熟悉的简书客户端作引,来讲讲hybrid:blush:
  • 开发者头条_程序员必装的App:感谢分享!已推荐到《开发者头条》:https://toutiao.io/posts/6iv75j 欢迎点赞支持!
    欢迎订阅《iOS与swift学习之路》 https://toutiao.io/subjects/35291
    我的月亮你的心:@开发者头条_程序员必装的App 不打广告会死?
  • 淮水依依:分析的很到位,网页的CDN加速这块,希望多讲一些:smile:
  • 亚当斯密:怎么迭代更新
  • 秋兰兮青青:对原生开发有哪些绝对优势?
    halohily:@秋兰兮青青 不光是开发速度快的问题。举个例子来说,用户量大的电商app,隔几天要推出一个新的活动,hybrid模式下只需要留出入口,前端随时开发随时发布新业务,这是原生模式不能及的。快速迭代,部分迭代的产品里这种模式很有优势
    秋兰兮青青: @秋兰兮青青 相对来说开发速度快点,但是层出不穷的兼容问题,性能问题,用户体验问题,修修改改,这个时间不短,用原生开发,前期虽然慢一点,但是可以一劳永逸的解决问题。
    6725d8a2234d:@秋兰兮青青 混合开发速度快啊,
  • 刘英滕:厉害了

本文标题:从【简书】iOS客户端,来谈谈Hybrid方案细节设计

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