Krush iOS应用架构

为避免撕逼,提前声明:本文纯属翻译,仅仅是为了学习,加上水平有限,见谅!

【原文】https://www.teehanlax.com/blog/krush-ios-architecture/

Krush iOS应用架构

在Teehan+Lax公司,到目前为止,我们已经着手开发Krush项目好几个月了。从iOS架构上讲Krush是一个非常有趣的应用,因
为它会涉及到很多iOS新手都会遇见的常见误区。特别是,需要访问API,拥有磁盘缓存,展示感兴趣内容的联网应用。在这篇文章中,我将会探讨一些关于应用方面的案例研究:为什么我们选择特定的方法,如何在实践中应用,以及事后我们应该怎么做。

在90天内,我们把Krush作为最小化可行产品(MVP:minimum viable product)推出,所以,“为什么”我们选择特定方法的动机主要是基于速度:如何快速的获取能向市场推出的特性和功能最小的集合所组成的可测试版本,并且在此后能多快的进行迭代?这些动机影响了我们做的决定,所以,如果你的动机不同,你可以通过这个镜头(lens)看待我们的决定。


案例研究 1:网络层

网络层主要由我的天才同事Brendan Lynch构建。网络层主要负责处理Krush发送的所有连接,他们调用服务器API或者CDN进行资源传输。所有的东西都使用一个公共接口。

我们选择使用更熟悉了网络操作技术,而不是使用像NSURLSession这样的新API。更具体就是,使用属于我们应用委托的请求客户端管理所有的网络活动。这个请求客户端持有一个NSOperationQueue,这是管理你网络请求的队列。

网络请求包含URL、参数和认证编码规范。请求对象知道如何构造认证NSURLRequests,在连接请求失败的情况下重新建立请求。网络请会求子类化NSOperation并遵守NSURLConnectionDataDelegate 协议。

如果网络请求失败或者超时,请求客户端将自动重新排队,这样会尝试数次,如果还是无法连接,那就彻底失败了。

每个操作都有回调块(block)。当一个操作完成或失败后,块(block)会被调用,同时传递网络返回的数据或操作的结果。下节我们会提及在请求客户端中定义的回调块(block),它会把数据转存到磁盘缓存中。

在实际中,这个网络架构可以正常工作。当一个请求失败后,它会自动重启,所以,我们的应用非常健壮。而不是使用新的iOS7 API,通过使用熟知的方法,我们可以快速的把产品推出来。

如果我们必须从头来过,为了减少代码量并利用iOS 7的后台fetch API,研究一下NSURLSession还是很值得的。我还想探讨一下把来自视图控制器的命令通过响应者链发送到应用委托中的想法,然后把他们发送到请求客户端中。通过这种方式我们可以完全解耦视图控制器和请求客户端。


案例研究 2:磁盘上的缓存

Krush是一个视觉化的应用,他下载并展示很多图片。一旦这些图片从JPEG格式解压成展示用的位图,将会占用大量的内存。在内存中保存应用的所有内容不是一个好的选择,但是在每次展示时都去下载可能会占用用户大量的网络资源。这个问题的解决办法是使用磁盘缓存。

对于可读性,Brendan使用他熟悉的SQLite构建了磁盘存储系统。然而,在我构建磁盘缓存的时候,他正在忙于构建网络层,而且我的SQLite功底很弱。所以,我使用了我熟悉的Core Data

Core Data不是一个对象持久化库,而是一个能数据持久化到磁盘存储器中的对象图管理框架。我们把它当做一个缓存;应用的每次启动都会删除存储的数据。

应用启动时应用最重要的方面之一。如果应用没有在一个合理的时间内启动的话,用户可能会放弃这个应用的。至于Krush,我们正在收集来自用户的反馈,并且客户端应用程序启动很慢。(⊙o⊙)…

我打开Instruments并在设备上测试了一下应用的启动时间。

哦~,应用创建了很多网络连接啊。在一次跟踪应用第一次启动的时候,我测出了170个网络请求。这表明我们提前创建了很多请求而不是按照需要创建。我增加了按需请求网络情况使得网络请求不那么乐观,这很容易改变。然而,这个改变导致了大量的界面卡顿。之后,我又测试了一遍。

我们发布的Krush使用了很简单的Core Data缓存,因为我们没有过多的时间投入到更复杂的东西上。堆栈有主线程上的单个管理对象上下文组成。不管怎样,我从来都不喜欢过早的进行优化;相比与此,我更喜欢测试——调整——测试这样的开发节奏。当我测试界面卡顿的时候,我立即就发现了问题:Core Data在主线程上阻塞了。

我做了些研究后最终决定采取不同的方式。请求客户端实例持有一个在自己队列中运行的后台上下文;后台队列和主线程队列共用一个持久化存储协调器(Persistent Store Coordinator)。

我们来看一个获取用户详情的网络请求示例。

用户对象已经存在于主管理对象上下文中,但是不需要后台上下文。为了保证对象已经存在于持久存储器中,我们必须保存主上下文。然后,从用户信息中获取对象id(objectId),并在网络请求的回调块(block)中从后台上下文获取相应的的用户对象。在后台线程,我们执行JSON解析,并在后台上下文中形成用户和其他对象之间的关系。最后,保存后台上下文,这会出发一个通知去把后台上下问的改变合并到主上下文中。相应的视图也会通过KVO进行更新。呼~。

结果很令人振奋。我们显著的改善了启动时间,并且整个界面页面的极为流畅。

理论上,我们做的所有改变都应该在后台管理对象向下文中进行。如果我必须重做的个方案的话,我会把主管理对象上下文模型实例设置为只读(语义上的)并且只能在后台上下文中执行更新。通过这种方式,可以避免在我需要在后台访问对象的时候必须先保存主上下文的麻烦。

这个经验教训告诉我们一定要对应用启动进行测试。优化界面和启动时间仅仅只是耗费了几天时间。如果我们再发布之前投入了这几天做优化,我们可能会在应用启动的时候获得更加流畅的用户体验,而不是在迭代之后。


案例研究 3:用户信息视图

Krush的用户信息是一个复杂的东西。不管是从设计的角度还是从编码的角度来看,正确的处理是很重要的。我们看到的设计中有三个tabKrushesInfluenceNetwork

image

不仅如此,这些tab需要进行模块化,因为对于一个品牌的用户页,我们可能会需要不同的tabs。这是一个很有趣的架构问题;如何以模块化的方式重用代码?

我们可以使用子视图控制器,但是我更想尝试一下数据驱动。我仅仅只使用了一个由UITableViewController控制的table view。这个控制器强引用了一个遵守协议的datasource

当选中不同的tab时,数据源就会发生改变。此外,当数据元发生改变时,table view也会重新加载。现在,当table view询问控制器应该展示什么的时候,它则会去查询数据源。

数据源用来填充我们自己写的选项卡选择控件。不同的数据源是否可用,取决于新式的用户是否是一个品牌。通过使用ReactiveCocoa,我们可以在viewDidLoad方法中获取视图控制器中数据源的状态。没有把布局方面的考量委托给数据源,这使得我们的table view控制器的逻辑非常简单。

每一个数据源窦泽提过像列表行数,行高,定制个别行cell等这样的信息。每一个数据源还有一个类属性和重用标识,这个重用标识用来在viewDidLoad方法中注册自定义的UITableViewCell子类。最后,每一个数据源也负责暴露将触发tableview重载的ReactiveCocoa信号。

当设计在项目的迭代中发生改变时,这种数据源方法也很有效。它仍然保持着代码的简洁,解耦。使用这种方法的一个缺点是,当把Network选项卡中的各个设计整合到Krushes选项卡中后,在不同的数据源间共享相同的逻辑就变得不是那么轻松了。我希望Objective-C在语言层面支持抽象类,因为这样就可以有助于减少数据源对象之间的重复代码。


案例研究 4:Feed模块中的MVVM

早期发布的应用版本有一个简单地反馈页面和简单地用户培训教程。当我们把它演示给同事时,大家都认为教程部分是最初用户体验的一个弱点。Geoff建议在用户第一次启动应用的时候整合信息卡片到feed模块中,向用户展示如何使用应用。这样,他们在可以使用应用之前就不需要记住教程中的用法说明。

在那时,feed视图控制器使用一个NSFetchedResultsController去展示存储在Core Data中的内容。不是把新用户培训卡逻辑整合到feed视图控制器中,我更想探索一下一个新出现的Objective-C架构模式:Model-View-ViewModel

简而言之,我们把视图控制器中展示所有内容的逻辑抽象到视图模型(view model)中,这对实际的UI是不可知的。视图模型只是提供Endorse和Save按钮是否显示或者用于特定列表单元格的图片等信息。我们也会把fetched results controller的协议代码从视图控制器中移动到视图模型(view model)中,这些代码会把培训模型插入到它所维护的内部数组中。

当用户达到feed的终点获取跟多数据或者当用户下拉刷新的时候,视图模型也会接收到通知。

当我们把话题标签整合到应用中时,这种架构很有效。使用相同的视图控制器仅仅是视图模型和展示逻辑不同。通过让不同的视图模型遵守视图控制器可以依赖的相同协议,我们能够让视图控制器不需要知道它展示什么东西,以及如何展示的。

很高兴这种方法对我们很有用。如果必须重做,我会尽力减少视图模型之间的重复代码。同样的,抽象类也可以在这儿起作用。


结论

在Teehan+Lax,这个项目令我们兴奋异常。在整个项目期间,我们学到了很多并且还在开发期间获得了诸多乐趣。我想通过分享一些在项目期间的经验教训,开发者可以开发出属于他们自己的了不起的应用。请叫我雷锋!

image

你可能感兴趣的:(Krush iOS应用架构)