精华内容
下载资源
问答
  • App架构设计

    千次阅读 2017-02-22 15:45:05
    App架构设计经验谈:技术选型 App架构设计经验谈:接口的设计 App架构设计经验谈:数据层的设计 App架构设计经验谈:业务层的设计 App架构设计经验谈:展示层的设计 展示层 展示层是三层架构中最复杂的一层了...

    App架构设计经验谈:技术选型

    App架构设计经验谈:接口的设计

    App架构设计经验谈:数据层的设计
    App架构设计经验谈:业务层的设计

    App架构设计经验谈:展示层的设计


    展示层

    展示层是三层架构中最复杂的一层了,需要考虑的包括但不限于界面布局、屏幕适配、文字大小、颜色、图片资源、提示信息、动画等等。展示层也是变化最频繁的一个层面,每天改得最多的就是界面了。因此,展示层也是最容易变得混乱不堪的一个层面。一个良好的展示层,应该有较好的可读性、健壮性、维护性、扩展性。


    三原则

    我在Android项目重构之路:界面篇中提到过三个原则,要设计好展示层,至少需要遵循好这三条基本的原则:

    1. 保持规范性:定义好开发规范,包括书写规范、命名规范、注释规范等,并按照规范严格执行;
    2. 保持单一性:布局就只做布局,内容就只做内容,各自分离好,每个方法、每个类,也只做一件事情;
    3. 保持简洁性:保持代码和结构的简洁,每个方法,每个类,每个包,每个文件,都不要塞太多代码或资源,感觉多了就应该拆分。

    关于这三个原则详细的解说,界面篇已经讲过的,我这里就不再重复。在此,我只做些补充。

    关于规范,Android方面,我已经分享过一套Android技术积累:开发规范,主要分为书写规范、命名规范、注释规范三部分。iOS方面,苹果已经有一套Coding Guidelines,主要属于命名方面的规范。当我们制定自己的开发规范时,首先就要遵守苹果的这份规范,在此基础上再加上自己的规范。

    最重要的不是开发规范的制定,而是开发规范的执行。如果没有按照开发规范去执行,那开发规范就等于形同虚设,那代码混乱的问题依然得不到解决。

    另外,Android系统本身已经对资源进行了很好的分离,字符串、颜色值、尺寸大小、图片、动画等等都用不同的xml文件定义。而iOS系统在这方面就逊色很多,只能自己实现,其中一种实现方案就是通过plist文件的方式实现和Android一样的机制。

    工程结构

    工程结构其实就是模块的划分,无非分为两类:按业务划分或按组件划分。

    比如一个电商App,可能会有首页、附近、分类、我的四大模块,工程结构也根据这四大模块进行划分,Android可能就分为了四个模块包:

    • com.domain.home 首页
    • com.domain.nearby 附近
    • com.domain.category 分类
    • com.domain.user 我的

    同样的,iOS则分为四个分组:home、nearby、category、user。

    之后,每个模块下相应的页面就放入相应的模块。那么,问题来了,商品详情页应该属于哪个模块呢?首页会跳转到商品详情页,附近也会跳转到商品详情页,分类也会跳转到商品详情页,用户查看订单时也能跳转到商品详情页。有些页面,并不能很明显的区分出属于哪个模块的。我接手过的,按业务划分的二手项目中(即不是由我搭建的项目),我要找一个页面时,我认为应该属于A模块的,但在A模块却找不到,问了同事才知道在B模块。类似的情况出现过很多次,而且不止出现在我身上,对业务不熟悉的开发人员都会出现这个问题。而且,对业务不熟悉的开发人员开发新的页面或功能时,如果对业务理解不深,划分出错,那也将成为问题,其他人员要找该页面时更难找到了。

    因此,我更喜欢按组件划分的工程结构,因为组件每个人都懂,不管对业务熟不熟悉,查找起来都明显方便很多。Android按组件划分大致如下:

    • com.domain.activities 存放所有的Activity
    • com.domain.fragments 存放所有的Fragment
    • com.domain.adapters 存放所有的Adapter
    • com.domain.services 存放所有的Service
    • com.domain.views 存放所有的自定义View
    • com.domain.utils 存放所有的工具类

    iOS的分组则大致如下:

    • controllers 存放所有ViewController
    • cells 存放所有Cell,包括TableViewCell和CollectionViewCell
    • views 存放所有自定义控件或对系统控件的扩展
    • utils 存放所有的工具类

    基类的定义

    Android的Activity、Fragment、Adapter,iOS的ViewController,分别定义一个基类,将大部分通用的变量和方法定义和封装好,将减少很多工作量,而且有了统一的设置,也会减少代码的混乱。比如我在Android项目重构之路:实现篇中提到的KBaseActivity和KBaseAdapter的实现就是例子,当然还可以抽离出更多变量和方法。

    每个Activity的onCreate()方法,一般分为三步:

    1. 变量的初始化;
    2. View的初始化;
    3. 加载数据。

    因此,其实可以将onCreate()方法拆分成三个方法:

    1. initVariables()
    2. initViews()
    3. loadData()

    在基类中将这三个方法定义为抽象方法,由子类去实现,这样,子类就不需要实现onCreate()方法了,只要实现更细化的上述三个方法即可。

    iOS的ViewController也是同样的方式,这里就不重复了。

    写在最后

    自此,该系列的文章暂时就完结了,方法论比较多,很少涉及到具体的实现。因为具体实现的方案很多,而且还要结合实际项目,无法说哪个方案好哪个方案差。但方法论大部分是想通的,所以,本系列主要讲方法论。


    业务层:


    业务层其实并不复杂,但是大部分开发人员对其职责并没有理解清楚,从而使其沦落为一个数据中转站。我之前分享过的Android项目重构之路系列中提到的核心层,其实就是这里所讲的业务层。但有不少读者反映,他们在实际项目中就只是做一下参数检查,然后直接调用API,与展示层对接的接口基本也与API的接口一致的。这样,业务层无疑就已经变为了一个数据中转站。

    业务层的职责

    所以,设计业务层之前,对业务层的职责要先真正理解清楚。这里,我举两个栗子说明一下。

    第一个是新用户注册的例子。注册时,界面上一般都会要求用户输入手机号、验证码、密码和确认密码。但是,API接口一般只会有三个参数:手机号、验证码和密码,不会有确认密码。因此,调用接口之前,密码和确认密码的一致性检查是必须的。同时,也要检查这些数据是否为空、手机号是否符合规范、验证码是否有效、密码有没有包含了特殊字符等。正确姿势就是当所有检查都通过了之后,才调用API接口。最后,调用注册接口成功后,可能还要再调用一次登录接口,并可能将用户登录信息缓存起来,方便用户下次启动应用时自动登录。所有这些都属于业务逻辑处理,也就是业务层的工作。

    第二个是涉及用户验证的例子。比如,在一个电商App,当用户浏览某个商品,点击购买时,App首先会判断用户是否已经登录,如未登录,则会跳转到登录页面让用户先登录。如果已经登录,但token已经过期,那需要先去获取新的token,之后才能进行下一步的购物操作。这些逻辑处理,也是业务层的工作。

    因此,简单点说,业务层就是处理业务逻辑,包括数据的检查、业务分支的处理等。比如上面第二个例子,可能很多人就会将用户是否已经登录的判断直接在界面上做处理,当确认登录后,token也是有效的之后,才调用业务层做购买商品的操作,这就是导致业务层沦落为API的数据中转站的直接表现。

    业务层的交互

    只有真正理解了业务层的职责之后,才能有效地设计业务层与外层的交互接口。

    业务层向下,与数据层交互;向上,与展示层交互。

    与数据层交互只是调用数据层的接口获取数据,而与展示层交互则需要提供接口给展示层调用。因为业务处理一般属于比较耗时的操作,主要在于底层的网络请求比较耗时,所以提供给展示层的接口数据结果应该以异步的方式提供,因此,接口上就需要提供个回调参数,返回业务处理之后的结果。我之前分享过的Android项目重构之路:实现篇有讲到一种实现方式,可参考。

    写在最后

    业务层可以说是一个数据加工场,处理核心的业务逻辑。其实,只要理解清楚了业务层的职责,业务层就不难实现。



    一个App,从根本上来说,就是对数据的处理,包括数据从哪里来、数据如何组织、数据怎么展示,从职责上划分就是:数据管理、数据加工、数据展示。相对应的也就有了三层架构:数据层、业务层、展示层。本文就先讲讲数据层的设计。

    数据层,是三层架构中的最底层,负责数据的管理。它主要的任务就是:

    1. 调用网络API,获取数据;
    2. 将数据缓存到本地;
    3. 将数据交付给上一层。

    根据这三个任务,数据层可以再拆分为三层:网络层、本地数据层、交付层。

    网络层

    网络层主要就是对网络API的封装。关于API的设计,该系列的第一篇文章接口的设计已经讲过一些。关于如何封装,可以参考Android项目重构之路系列的架构篇实现篇,其中接口层和本文的网络层是一样的。

    还有一些在前面的文章中没有提及到的,在此做一些补充。

    首先是不同网络状态的处理。当网络不可用时,则不应该再去调用API;当网络可用,但不是WIFI时,有些比较耗流量的操作也应该禁止,比如上传和下载大文件;当网络状态不同时,还可以采用不同的网络策略,比如,当网络为WIFI时,当前API可以返回更多更全面的数据,还可以预先加载相关联的其他API。

    其次,为了节省流量,接口的设计上可以对数据进行简化。例如,对于一些列表类的接口,可以这么设计:只返回更新的部分,比如,上一次请求返回了10条按时间排序的数据,第一条数据为最新的,id为101,当发起下一次请求时,将101的id作为参数调用API,API查到该id,发现该id之后又新增了两条数据,API则只返回新增的这两条数据。

    另外,为了保证程序的健壮性,调用API时,对入参的合法性检查也是很有必要的。而且,也应该定义好本地的错误码和错误信息,保证每个错误都能正常解析。

    本地数据层

    本地数据层主要就是做缓存处理,这需要设计好一套缓存策略。设计缓存策略时,有几个问题需要考虑清楚:

    1. 哪些需要缓存?哪些不需要缓存?
    2. 缓存在哪里?数据库?文件?还是内存?
    3. 缓存时间多长?

    哪些需要缓存?

    将所有数据都缓存是不明智的,不同的数据应该有不同的缓存策略,比如一个电商App,首页的商品列表数据应该缓存,而且缓存时间应该比较长,而每个商品的详情数据就没必要缓存或缓存时间很短。对于一份数据需不需要缓存,判断标准可以是:用户查看该数据的频率高不高?首页商品列表是用户每次启动都会看到的,而每个商品的详情用户最多只看几次。

    缓存在哪里?

    从内存读取数据是最快的,但内存非常有限。因此,内存一般只用来缓存使用频率非常高的数据。

    文件缓存主要就是图片、音频、视频了。

    数据库可以保存大量数据,主要就是用于保存商品列表、聊天记录之类的关系型数据。

    然而,不管缓存在哪里,都需要限定好缓存的容量,要定期清理,不然会越积越多。

    缓存时间多长?

    首先,每份缓存数据都应该设置一个缓存的有效时间,有效期的起始时间以最后一次被调用的时间为准,当该数据长时间没有再被调用到时,就应该从缓存中清理掉。

    缓存的有效时间应该设多长呢?可以短至一分钟,长至一星期甚至一个月,具体因数据而异。一般内存的缓存时间不宜太长,程序退出基本就要全部清理了。文件缓存可以设置保留一天或一个星期,可以每隔一天清理一次。数据库缓存再久一些也无所谓,但最好还是不要超过一个月。

    交付层

    交付层其实就是一个向上层开放的交互接口层,是上层向数据层获取数据的入口。上层向数据层请求数据,它是不关心数据层的数据是从缓存获取还是从网络获取的,它只关心结果,数据层能给到它想要的数据结果就OK了。因此,交付层主要就是定义一堆开放的接口或协议。

    如果接口或协议非常多,那么,将接口或协议按照模块划分也是有必要的。比如微信,按模块划分有:IM、公众号、朋友圈、钱包、购物、游戏等等。模块之间应该尽量相对独立、松耦合。

    写在最后

    数据层如果再扩展,还可以再加入日志管理,这里就不再展开讲了。上面内容讲得也比较乱,有哪里讲得不好的地方欢迎吐槽。

    接口的设计

    App与服务器的通信接口如何设计得好,需要考虑的地方挺多的,在此根据我的一些经验做一些总结分享,旨在抛砖引玉。

    安全机制的设计

    现在,大部分App的接口都采用RESTful架构,RESTFul最重要的一个设计原则就是,客户端与服务器的交互在请求之间是无状态的,也就是说,当涉及到用户状态时,每次请求都要带上身份验证信息。实现上,大部分都采用token的认证方式,一般流程是:

    1. 用户用密码登录成功后,服务器返回token给客户端;
    2. 客户端将token保存在本地,发起后续的相关请求时,将token发回给服务器;
    3. 服务器检查token的有效性,有效则返回数据,若无效,分两种情况:
      • token错误,这时需要用户重新登录,获取正确的token
      • token过期,这时客户端需要再发起一次认证请求,获取新的token

    然而,此种验证方式存在一个安全性问题:当登录接口被劫持时,黑客就获取到了用户密码和token,后续则可以对该用户做任何事情了。用户只有修改密码才能夺回控制权。

    如何优化呢?第一种解决方案是采用HTTPS。HTTPS在HTTP的基础上添加了SSL安全协议,自动对数据进行了压缩加密,在一定程序可以防止监听、防止劫持、防止重发,安全性可以提高很多。不过,SSL也不是绝对安全的,也存在被劫持的可能。另外,服务器对HTTPS的配置相对有点复杂,还需要到CA申请证书,而且一般还是收费的。而且,HTTPS效率也比较低。一般,只有安全要求比较高的系统才会采用HTTPS,比如银行。而大部分对安全要求没那么高的App还是采用HTTP的方式。

    我们目前的做法是给每个接口都添加签名。给客户端分配一个密钥,每次请求接口时,将密钥和所有参数组合成源串,根据签名算法生成签名值,发送请求时将签名一起发送给服务器验证。类似的实现可参考OAuth1.0的签名算法。这样,黑客不知道密钥,不知道签名算法,就算拦截到登录接口,后续请求也无法成功操作。不过,因为签名算法比较麻烦,而且容易出错,只适合对内的接口。如果你们的接口属于开放的API,则不太适合这种签名认证的方式了,建议还是使用OAuth2.0的认证机制。

    我们也给每个端分配一个appKey,比如Android、iOS、微信三端,每个端分别分配一个appKey和一个密钥。没有传appKey的请求将报错,传错了appKey的请求也将报错。这样,安全性方面又加多了一层防御,同时也方便对不同端做一些不同的处理策略。

    另外,现在越来越多App取消了密码登录,而采用手机号+短信验证码的登录方式,我在当前的项目中也采用了这种登录方式。这种登录方式有几种好处:

    1. 不需要注册,不需要修改密码,也不需要因为忘记密码而重置密码的操作了;
    2. 用户不再需要记住密码了,也不怕密码泄露的问题了;
    3. 相对于密码登录其安全性明显提高了。

    接口数据的设计

    接口的数据一般都采用JSON格式进行传输,不过,需要注意的是,JSON的值只有六种数据类型:

    • Number:整数或浮点数
    • String:字符串
    • Boolean:true 或 false
    • Array:数组包含在方括号[]中
    • Object:对象包含在大括号{}中
    • Null:空类型

    所以,传输的数据类型不能超过这六种数据类型。以前,我们曾经试过传输Date类型,它会转为类似于"2016年1月7日 09时17分42秒 GMT+08:00"这样的字符串,这在转换时会产生问题,不同的解析库解析方式可能不同,有的可能会转乱,有的可能直接异常了。要避免出错,必须做特殊处理,自己手动去做解析。为了根除这种问题,最好的解决方案是用毫秒数表示日期。

    另外,以前的项目中还出现过字符串的"true"和"false",或者字符串的数字,甚至还出现过字符串的"null",导致解析错误,尤其是"null",导致App奔溃,后来查了好久才查出来是该问题导致的。这都是因为服务端对数据没处理好,导致有些数据转为了字符串。所以,在客户端,也不能完全信任服务端传回的数据都是对的,需要对所有异常情况都做相应处理。

    服务器返回的数据结构,一般为:

    {
        code:0,
        message: "success",
        data: { key1: value1, key2: value2, ... }
    }
    
    • code: 返回码,0表示成功,非0表示各种不同的错误
    • message: 描述信息,成功时为"success",错误时则是错误信息
    • data: 成功时返回的数据,类型为对象或数组

    不同错误需要定义不同的返回码,属于客户端的错误和服务端的错误也要区分,比如1XX表示客户端的错误,2XX表示服务端的错误。这里举几个例子:

    • 0:成功
    • 100:请求错误
    • 101:缺少appKey
    • 102:缺少签名
    • 103:缺少参数
    • 200:服务器出错
    • 201:服务不可用
    • 202:服务器正在重启

    错误信息一般有两种用途:一是客户端开发人员调试时看具体是什么错误;二是作为App错误提示直接展示给用户看。主要还是作为App错误提示,直接展示给用户看的。所以,大部分都是简短的提示信息。

    data字段只在请求成功时才会有数据返回的。数据类型限定为对象或数组,当请求需要的数据为单个对象时则传回对象,当请求需要的数据是列表时,则为某个对象的数组。这里需要注意的就是,不要将data传入字符串或数字,即使请求需要的数据只有一个,比如token,那返回的data应该为:

    // 正确
    data: { token: 123456 }
    
    // 错误
    data: 123456
    

    接口版本的设计

    接口不可能一成不变,在不停迭代中,总会发生变化。接口的变化一般会有几种:

    • 数据的变化,比如增加了旧版本不支持的数据类型
    • 参数的变化,比如新增了参数
    • 接口的废弃,不再使用该接口了

    为了适应这些变化,必须得做接口版本的设计。实现上,一般有两种做法:

    1. 每个接口有各自的版本,一般为接口添加个version的参数。
    2. 整个接口系统有统一的版本,一般在URL中添加版本号,比如http://api.domain.com/v2。

    大部分情况下会采用第一种方式,当某一个接口有变动时,在这个接口上叠加版本号,并兼容旧版本。App的新版本开发传参时则将传入新版本的version。

    如果整个接口系统的根基都发生变动的话,比如微博API,从OAuth1.0升级到OAuth2.0,整个API都进行了升级。

    有时候,一个接口的变动还会影响到其他接口,但做的时候不一定能发现。因此,最好还要有一套完善的测试机制保证每次接口变更都能测试到所有相关层面。

    写在最后

    关于接口设计,暂时想到的就这么多了。各位看官看完觉得有遗漏或有哪些需要优化的欢迎提出一起讨论。


    技术选型


    当你做架构设计时,必然会面临技术选型的抉择,不同的技术方案,架构也可能完全不同。有哪些技术选型需要做决策呢?比如,App是纯原生开发,还是Web App,抑或Hybrid App?iOS开发,语言上是选择Objective-C还是Swift?架构模式用MVC,还是MVP,或者MVVM?下面根据我的一些经验对某些方面做点总结分享。

    原生/H5

    关于用原生好,还是用H5好的争论从没间断过。但我觉得,脱离了实际场景来讨论孰好孰坏意义不大。就说我们目前正在做的项目,先说明下背景:

    1. 不止要做Android和iOS App,也要做微信公众号;
    2. H5人员缺乏,只有一两个兼职的可用,而且不可控因素很高;
    3. 我们对原生比较熟;
    4. 开发时间只有半个月。

    首先,需求上来说,大部分页面用H5实现,可以减少很多工作量。但因为不可控因素太高,而时间又短,风险太大。而我们对原生比较熟,开发效率比较高,很多东西我也控制得了,风险相对比较低。而且,我们的主推产品是App,微信属于辅助性产品,所以,微信要求也没那么高。因此,我决定以原生为主,H5为辅,App大部分页面用原生完成,小部分用WebView加载H5。

    另外,WebView加载H5也有两种模式,一种是加载服务器的H5页面,一种是加载本地的H5页面。加载服务器的H5页面比较简单,WebView只要load一下URL就可以了。加载本地的H5页面,则需要将H5文件存放在本地,包括关联的CSS和JS文件。这种方式相对比较复杂,不过,加载速度会比第一种快很多。我们当前项目基于上面考虑,只能选择第一种方案。

    如果人员和时间资源充足的话,那又如何选型呢?毫无疑问,我会以H5为主,微信和App都有的页面统一用H5,App专有的部分,比如导航栏、标题栏、登录等,才用原生实现。另外,WebView里的H5有点击事件时,也许是URL链接,也许是调用JS的,都不会让它直接在该WebView里做跳转,需要拦截下来做些原生处理后跳转到一个新的原生页面,原生页面也许嵌入另一个WebView,用来展示新的H5页面。这是简单的例子,关于Hybrid App详细的设计,以后再讲。另外,关于H5,绝对是大趋势,强烈建议所有App开发人员都去学习。

    Objective-C/Swift

    我在项目中选择了Swift,主要基于三个原因:

    1. Swift真的很简洁,生产效率很高;
    2. Swift取代Objective-C是必然的趋势;
    3. 目前iOS只有我一个人开发,不需要顾虑到团队里没人懂Swift。

    如果你的团队里没人懂Swift,那还是乖乖用Objective-C吧;如果有一两个懂Swift的,那可以混合开发,并让不懂的人尽快学会Swift;如果都懂了,不用想了,直接上Swift吧。

    当语言上选择了Swift,相应的一些第三方库也面临着选型。比如,依赖库管理,Objective-C时代大部分用CocoaPods,Swift时代,我更喜欢Carthage。Carhage是用Swift写的,和CocoaPods相比,轻耦合,也更灵活。我个人也不太喜欢CocoaPods,使用起来比较麻烦,耦合性也较高,我使用过程中也经常出问题,而且还总是不知道该怎么解决,要移除时也是非常麻烦。

    再推荐几个关于Swift的第三方库:

    1. Alamofire:Swift版本的网络基础库,和AFNetworking是同一个作者
    2. AlamofireImage:基于Alamofire的图片加载库
    3. ObjectMapper:Swift版本的Json和Model转换库
    4. AlamofireObjectMapper:Alamofire的扩展库,结合了ObjectMapper,自动将JSON的Response数据转换为了Swift对象

    MVC/MVP/MVVM

    先分别简单介绍下这三个架构模式吧:

    • MVC:Model-View-Controller,经典模式,很容易理解,主要缺点有两个:

      1. View对Model的依赖,会导致View也包含了业务逻辑;
      2. Controller会变得很厚很复杂。
    • MVP:Model-View-Presenter,MVC的一个演变模式,将Controller换成了Presenter,主要为了解决上述第一个缺点,将View和Model解耦,不过第二个缺点依然没有解决。

    • MVVM:Model-View-ViewModel,是对MVP的一个优化模式,采用了双向绑定:View的变动,自动反映在ViewModel,反之亦然。

    架构模式上,我不会推崇说哪种模式好,每种模式都各有优点,也各有极限性。越高级的模式复杂性越高,实现起来也越难。最近火热的微服务架构,比起MVC,复杂度不知增加了多少倍。

    我在实际项目中思考架构时,也不会想着要用哪种模式,我只思考现阶段,以现有的人力资源和时间资源,如何才能更快更好地完成需求,适当考虑下如何为后期扩展或重构做准备。就说我前段时间分享的Android项目重构之路系列中讲的那个架构,确切地说,都不属于上面三种架构模式之一。

    写在最后

    技术选型,决策关键不在于每种技术方案的优劣如何,而在于你团队的水平、资源的多寡,要根据实际情况选择最适合你们当前阶段的架构方案。当团队拓展了,资源也充足了,肯定也是需要再重构的,到时再思考其他更合适更优秀的方案。



    展开全文
  • App架构设计经验谈

    2017-05-02 10:17:28
    App架构设计经验谈:接口的设计 App架构设计经验谈:技术选型 App架构设计经验谈:数据层的设计 App架构设计经验谈:业务层的设计 App架构设计经验谈:展示层的设计:接口的设计
    展开全文
  • Android App 架构

    千次阅读 2016-10-27 23:16:57
    App 架构图 结构清晰

    App 架构图

    这里写图片描述

    结构清晰

    展开全文
  • IOS APP 架构设计(一)

    万次阅读 2019-06-25 15:17:25
    IOS APP 架构设计一,APP架构概述 一,APP架构概述 IOS 架构的5中模式: 标准的CocoaModel-View-Controller(MVC)是Apple在示例项目中所采用的设计模 式。它是 Cocoa app 中最为常⻅的架构,同时也是在 Cocoa 中讨论...

    一,APP架构概述

    1. 应用架构

    App 架构是软件设计的一个分支,它关心如何设计一个 app 的结构。具体来说,它关注于两个 方面:如何将 app 分解为不同的接口和概念层次部件,以及这些部件之间和自身的不同操作中 所使用的控制流和数据流路径。
    我们通常使用简单的框图来解释 app 的架构。比如,Apple 的 MVC 模式可以通过 model、 view 和 controller 三层结构来描述。

    在这里插入图片描述
    上面框图中的模块展示了这个模式中不同名字的三个层次。在一个 MVC 项目中,绝大部分的代 码都会落到其中某个层上。箭头表示了这些层进行连接的方式。
    但是,这种简单的框图几乎无法解释在实践中模式的操作方式。这是因为在实际的 app 架构中, 部件的构建有非常多的可能性。事件流在层中穿梭的方式是什么?部件之间是否应该在编译期 间或者运行时持有对方?要怎么读取和修改不同部件中的数据?以及状态的变更应该以哪条路 径在 app 中穿行?

    2.Model 和 View

    在最高的层级上,app 架构其实就是一套分类,app 中不同的部件会被归纳到某个类型中去。 在本书中,我们将这些不同的种类叫做层次:一个层次指的是,遵循一些基本规则并负责特定 功能的接口和其他代码的集合。
    Model 层和 View 层是这些分类中最为常⻅的两个。
    Model 层是 app 的内容,它不依赖于 (像 UIKit 那样的) 任何 app 框架。也就是说,程序员对 model 层有完全的控制。Model 层通常包括 model 对象 (在录音 app 中的例子是文件夹和录音对象) 和协调对象 (比如我们的 app 例子中的负责在磁盘上存储数据的 Store 类型)。被存储在 磁盘上的那部分 model 我们称之为文档 model (documentation model)。
    View 层是依赖于 app 框架的部分,它使 model 层可⻅,并允许用戶进行交互,从而将 model 层转变为一个 app。当创建 iOS 应用时,view 层几乎总是直接使用 UIKit。不过,我们也会看 到在有些架构中,会使用 UIKit 的封装来实现不同的 view 层。另外,对一些其他的像是游戏这 样的自定义应用,view 层可以不是 UIKit 或者 AppKit,它可能是 SceneKit 或者 OpenGL 的某 种封装。
    有时候,我们选择使用结构体或者枚举来表示 model 或者 view 的实例,而不使用类的对象。 在实践中,类型之间的区别非常重要,但是当我们在 model 层中谈到对象、结构体和枚举时, 我们会将三者统一地称为 model 对象。类似地,我们也会把 view 层的实例叫做 view 对象,实 际上它们也可能是对象、结构体或者枚举。
    View 对象通常会构成一个单一的 view 层级,在这个层级中,所有的 view 对象通过树结构的方 式连接起来。在树的根部是整个屏幕,屏幕中存在若干窗口,接下来在树的分支和叶子上是更 多的小 view。类似地,view controller 也通常会形成 view controller 层级。不过,model 对 象却不需要有明确的层级关系,在程序中它们可以是互不关联的独立 model。
    当我们提到 view 时,通常指的是像一个按钮或者一个文本 label 这样的单一 view 对象。当我 们提到 model 时,我们通常指的也是像一个 Recording 实例或者 Folder 实例这样的单个 model 对象。在该话题的大多数文献中,“model” 在不同上下文中指的可能是不同的事情。它 可以指代一个 model 层,model 层中的具体的若干对象,文档 model,或者是 model 层中不 关联的文档。虽然可能会显得啰嗦,我们还是会尝试在本书中尽量明确地区分这些不同含义。

    为什么 Model 和 View 的分类会被认为是基础中的基础

    当然啦,就算不区分 model 层和 view 层,写出一个 app 也是绝对可能的。比如说,在一个简 单的对话框中,通常就没有独立的 model 数据。在用戶点击 OK 按钮的时候,我们可以直接从 用戶界面元素中读取状态。不过通常情况下,model 层的缺失,会让程序的行为缺乏对于清晰 规则的依据,这会使得代码难以维护。
    定义一个 model 层的最重要的理由是,它为我们的程序提供一个表述事实的单一来源,这会让 逻辑清晰,行为正确。这样一来,我们的程序便不会被应用框架中的实现细节所支配。
    应用框架为我们提供了构建 app 所需要的基础设施。在本书中,我们使用 Cocoa - 或者更精确 说,根据目标平台,使用 UIKit,AppKit 或者 WatchKit - 来作为应用框架。

    如果 model 层能做到和应用框架分离,我们就可以完全在 app 的范围之外使用它。我们可以很 容易地在另外的测试套件中运行它,或者用一个完全不同的应用框架重写新的 view 层。这个 model 层将能够用于 Android,macOS 或者 Windows 版本的 app 中。

    3.App 的本质是反馈回路

    View 层和 model 层需要交流。所以,两者之间需要存在连接。假设 view 层和 model 层是被清
    晰地分开,而且不存在无法解耦的联结的话,两者之间的通讯就需要一些形式的翻译:
    在这里插入图片描述

    从根本上说,用戶界面是一个同时负责展示和输入功能的反馈设备,所以毫无疑问,这导致的 结果就是一个反馈回路。每个 app 设计模式所面临的挑战是如何处理这张图表中箭头所包含的 交流,依赖和变换。
    在 model 层和 view 层之间不同的路径拥有不同的名字。用戶发起的事件会导致 view 的响应, 我们把由此引起的代码路径称为 view action,像是点击按钮或者选中 table view 中的某一行 就属于 view action。当一个 view action 被送到 model 层时,它会被转变为 model action (或 者说,让 model 对象执行一个 action 或者进行更新的命令)。这种命令也被叫做一个消息 (特别 在当 model 是被 reducer 改变时,我们会这么称呼它)。将 view action 转变为 model action 的操作,以及路径上的其他逻辑被叫做交互逻辑。

    一个或者多个 model 对象上状态的改变叫做 model 变更。Model 的变更通常会触发一个
    model 通知,比如说从 model 层发出一个可观测的通知,它描述 model 层中什么内容发生了 改变。当 view 依赖于 model 数据时,通知会触发一个 view 变更,来更改 view 层中的内容。 这些通知可以以多种形式存在:Foundation 中的 Noti?cation,代理,回调,或者是其他机制, 都是可以的。将 model 通知和数据转变为 view 更改的操作,以及路径上的其他逻辑被叫做表 现逻辑。
    根据 app 模式的不同,有些状态可能是在文档 model 之外进行维护的,这样一来,更新这些状 态的行为就不会追随文档 model 的路径。在很多模式中的导航状态就这种行为的一个常⻅例 子,在 view 层级中的某个部分 (或者按照 Cocoa Storyboard 中使用的术语,将它称为 scene) 可能会被换出或者换入层级中。
    在 app 中非文档 model 的状态被叫做 view state。在 Cocoa 里,大部分 view 对象都管理着它 们自己的 view state,controller 对象则管理剩余的 view state。在 Cocoa view state 的框图 中,通常会有加在反馈回路上的捷径,或者单个层自身进行循环。在有一些架构中,view state 不属于 controller 层,而是属于 model 层的部分 (不过,根据定义,view controller 并不是文 档 model 的一部分)。
    当所有的状态都在 model 层中被维护,而且所有的变更都通过完整的反馈回路路径进行传递 时,我们就将它称为单向数据流。当任意的 view 对象或者中间层对象只能够通过 model 发出 的通知来进行创建和更新 (换句话说,view 或者中间层不能通过捷径来更新自身或者其他的 view) 时,这个模式通常就是单向的。

    4.架构技术

    Apple 平台的标准 Cocoa 框架提供了一些架构工具。Noti?cation 将值从单一源广播给若干个 收听者。键值观察 (KVO) 可以将某个对象上属性的改变报告给另一个对象。然而,Cocoa 中的 架构工具十分有限,我们将会使用到一些额外的框架。
    本书中使用到的第三方技术中包含了响应式编程。响应式编程也是一种用来交流变更的工具, 不过和通知或者 KVO 不同的是,它专注于在源和目标之间进行变形,让逻辑可以在部件之间传 输信息的同时得以表达。
    我们可以使用像是响应式编程或者 KVO 这样的技术创建属性绑定。绑定接受一个源和一个目 标,无论何时,只要源发生了变化,目标也将被更新。这和手动进行观察在语法上有着不同, 我们不再需要写观察的逻辑,而只需要指定源和目标,接下来框架将会为我们处理其余部分的 工作。

    macOS 上的 Cocoa 包含有 Cocoa 绑定技术,它是一种双向绑定,所有的可观察对象同时也是 观察者,在一个方向上建立绑定连接,会在反方向也创建一个连接。不论是 (在MVVM-C 的章
    节中用到的) RxCocoa,还是 (MAVB 章节 中用到的) CwlViews,都不是双向绑定的。所以,在
    本书中,所有关于绑定的讨论都只涉及到单向绑定。

    5.App 任务

    要让程序正常工作,view 必须依赖于 model 数据来生成和存在,我们配置 view,让它可以对
    model 进行更改,并且能在 model 更新时也得到更新。 所以,我们需要决定在 app 中如何执行下列任务:

    1. 构建—谁负责构建model和view,以及将两者连接起来?
    2. 更新model—如何处理viewaction?
    3. 改变view—如何将model的数据应用到view上去?
    4. viewstate—如何处理导航和其他一些modelstate以外的状态?
    5. 测试—为了达到一定程度的测试覆盖,要采取怎样的测试策略?

    对于上面五个问题的回答,是构成 app 设计模式的基础要件。在本书中,我们会逐一研究这些 设计模式。

    6.IOS 架构的5中模式:

    IOS 架构的5中模式:

    • 标准的CocoaModel-View-Controller(MVC)是Apple在示例项目中所采用的设计模 式。它是 Cocoa app 中最为常⻅的架构,同时也是在 Cocoa 中讨论架构时所采用的基 准线。
    • Model-View-ViewModel+协调器(MVVM-C)是MVC的变种,它拥有单独的 “view-model” (视图模型) 和一个用来管理 view controller 的协调器。MVVM 使用数据 绑定 (通常会和响应式编程一起使用) 来建立 view-model 层和 view 层之间的连接。
    • Model-View-Controller+ViewState(MVC+VS)这种模式将所有的viewstate集中到 一个地方,而不是让它们散落在 view 和 view controller 中。这和 model 层所遵循的规 则相同。
    • Model适配器-View绑定器(ModelAdapter-ViewBinder,MAVB)是本书的一位作者所 使用的实验性质的架构。MAVB 专注于构建声明式的 view,并且抛弃 controller,采用 绑定的方式来在 model 和 view 之间进行通讯。
    • Elm架构(TEA)与MVC或者MVVM这样的常⻅架构完全背道而驰。它使用虚拟view 层级来构建 view,并使用 reducer 来在 model 和 view 之间进行交互。

    二,APP设计常用的5种模式概览

    1. Model-View-Controller

    在 Cocoa MVC 中,一小部分 controller 对象负责处理 model 或者 view 层范畴之外的所有任 务。
    这意味着,controller 层接收所有的 view action,处理所有的交互逻辑,发送所有的 model action,接收所有的 model 通知,对所有用来展示的数据进行准备,最后再将它们应用到 view 的变更上去。如果我们去看一下介绍一章中的 app 反馈回路的图,会发现在 model 和 view 之
    间的箭头上,几乎每个标签都是 controller。而且要知道,在这幅图中,构建和导航任务并没有 标注出来,它们也会由 controller 来处理。
    下面是 MVC 模式的框图,它展示了一个 MVC app 的主要通讯路径:

    在这里插入图片描述
    图中的虚线部分代表运行时的引用,view 层和 model 层都不会直接在代码中引用 controller。 实线部分代表编译期间的引用,controller 实例知道自己所连接的 view 和 model 对象的接口。
    如果我们在这个图标外部描上边界的话,就得到了一个 MVC 版本的 app 反馈回路。注意在图 表中其他的路径并不参与整个反馈回路的路径 (也就是 view 层和 controller 层上那些指向自身 的箭头)。

    1. 构建
      App 对象负责创建最顶层的 view controller,这个 view controller 将加载 view,并且知道应 该从 model 中获取哪些数据,然后把它们显示出来。Controller 要么显式地创建和持有 model 层,要么通过一个延迟创建的 model 单例来获取 model。在多文档配置中,model 层由更低层 的像是 UIDocument 或 NSDocument 所拥有。那些和 view 相关的单个 model 对象,通常会 被 controller 所引用并缓存下来。
    2. 更改 Model
      在 MVC 中,controller 主要通过 target/action 机制和 (由 storyboard 或者代码进行设置的) delegate 来接收 view 事件。Controller 知道自己所连接的 view,但是 view 在编译期间却没有 关于 controller 接口的信息。当一个 view 事件到达时,controller 有能力改变自身的内部状态, 更改 model,或者直接改变 view 层级。
    3. 更改 View
      在我们所理解的 MVC 中,当一个更改 model 的 view action 发生时,controller 不应该直接去 操作 view 层级。正确的做法是,controller 去订阅 model 通知,并且在当通知到达时再更改 view 层级。这样一来,数据流就可以单向进行:view action 被转变为 model 变更,然后 model 发送通知,这个通知最后被转为 view 变更。
    4. View State
      View state 可以按需要被 store 在 view 或者 controller 的属性中。相对于影响 model 的 view action,那些只影响 view 或 controller 状态的 action 则不需要通过 model 进行传递。对于 view state 的存储,可以结合使用 storyboard 和 UIStateRestoring 来进行实现,storyboard 负责记录活跃的 controller 层级,而 UIStateRestoring 负责从 controller 和 view 中读取数据。
    5. 测试
      在 MVC 中,view controller 与 app 的其他部件紧密相连。边界的缺失使得为 controller 编写 单元测试和接口测试十分困难,集成测试是余下的为数不多的可行测试手段之一。在集成测试 中,我们构建相连接的 view、model 和 controller 层,然后操作 model 或者 view,来测试是 否能得到我们想要的结果。
      集成测试的书写非常复杂,而且它涵盖的范围太广了。它不仅仅测试逻辑,也测试部件是如何 连接的 (虽然在一些情况下和 UI 测试的⻆度并不相同)。不过,在 MVC 中通过集成测试,通常 达到 80% 左右的测试覆盖率是有可能的。
    • MVC 的重要性

    因为 Apple 在所有的实例项目中都使用了这种模式,加上 Cocoa 本身就是针对这种模式设计 的,所以 Cocoa MVC 成为了 iOS,macOS,tvOS 和 watchOS 上官方认证的 app 架构模式。

    • 历史

    MVC 这个名字第一次被提出是在 1979 年,Trygve Reenskaug 用它来描述 Smalltalk-76 上已 经存在的 “template pattern” 应用。在他和 Adele Goldberg 讨论了术语方面的问题后,MVC 的名字被确定下来 (之前的名字包括 Model-View-Editor 和 Model-View-Tool-Editor 等)。
    在原本的构想中,view 是直接 “附着” 在 model 层上,并观察所有 model 变化的。Controller 存在的目的仅仅是捕捉用戶事件,并把它们转发给 model。这两个特性都是 Smalltalk 运行方 式的产物,它们并不是为了现代的 app 框架所设计的,所以今天这种 MVC 的原始构想已经几 乎绝迹了。
    Cocoa 中的 MVC 实现可以追溯到大约 1997 年的 NeXTSTEP 4 的年代。在那之前,所有现在 controller 所担当的⻆色,通常都由一个 (像是 NSWindow 那样的) 高层 view 类来扮演。之后, 从原始的 Smalltalk 的 MVC 实现中所发展出的理念是分离展示部分,也就是 view 层和 model 层应该被完全隔离开,这带来了一个强烈的需求,那就是要引入一个支持对象来辅助两者之间 的通讯。NeXTSTEP 中 controller 的概念和 Taligent 稍早的 Model-View-Presenter 中的 presenter (展示器) 很相似。不过,在现在 Model-View-Presenter 这个名字通常被用来指代那 些通过协议从 controller 中将 view 抽象出来的类似 MVC 的模式。

    2. Model-View-ViewModel+协调器

    MVVM 和 MVC 类似,也是通过基于场景 (scene,view 层级中可能会在导航发生改变时切入或者换出的子树) 进行的架构。相较于 MVC,MVVM 在每个场景中使用 view-model 来描述场景中的表现逻辑和交互逻辑。

    View-model 在编译期间不包含对 view 或者 controller 的引用。它暴露出一系列属性,用来描 述每个 view 在显示时应有的值。把一系列变换运用到底层的 model 对象后,就能得到这些最 终可以直接设置到 view 上的值。实际将这些值设置到 view 上的工作,则由预先建立的绑定来 完成,绑定会保证当这些显示值发生变化时,把它设定到对应的 view 上去。响应式编程是用来 表达这类声明和变换关系的很好的工具,所以它天生就适合 (虽说不是严格必要) 被用来处理
    view-model。在很多时候,整个 view-model 都可以用响应式编程绑定的方式,以声明式的形 式进行表达。

    在理论上,因为 view-model 不包含对 view 层的引用,所以它是独立于 app 框架的,这让对于 view-model 的测试也可以独立于 app 框架。
    由于 view-model 是和场景耦合的,我们还需要一个能够在场景间切换时提供逻辑的对象。在 MVVM-C 中,这个对象叫做协调器 (coordinator)。协调器持有对 model 层的引用,并且了解 view controller 树的结构,这样,它能够为每个场景的 view-model 提供所需要的 model 对象。
    和 MVC 不同,MVVM-C 中的 view controller 从来都不会直接引用其他的 view controller (所 以,也不会引用其他的 view-model)。View controller 通过 delegate 的机制,将 view action 的信息告诉协调器。协调器据此显示新的 view controller 并设置它们的 model 数据。换句话 说,view controller 的层级是由协调器进行管理的,而不是由 view controller 来决定的。

    这些特性所形成的架构的总体结构如下图所示:

    在这里插入图片描述

    如果我们忽略掉协调器,那么这张图表就很像 MVC 了,只不过在 view controller 和 model 之 间加入了一个阶段。MVVM 将之前在 view controller 中的大部分工作转移到了 view-model 中,但是要注意,view-model 并不会在编译时拥有对 view controller 的引用。
    View-model 可以从 view controller 和 view 中独立出来,也可以被单独测试。同样,view controller 也不再拥有内部的 view state,这些状态也被移动到了 view-model 中。在 MVC 中 view controller 的双重⻆色 (既作为 view 层级的一部分,又负责协调 view 和 model 之间的交 互),减少到了单一⻆色 (view controller 仅仅只是 view 层级的一部分)。
    协调器模式的加入进一步减少了 view controller 所负责的部分:现在它不需要关心如何展示其 他的 view controller 了。因此,这实际上是以添加了一层 controller 接口为代价,降低了 view controller 之间的耦合。

    1. 构建
      对于 model 的创建和 MVC 中的保持不变,通常它是一个顶层 controller 的职责。不过,单独
      的 model 对象现在属于 view-model,而不属于 view controller。
      初始的 view 层级的创建和 MVC 中的一样,通过 storyboard 或者代码来完成。和 MVC 不同的 是,view controller 不再直接为每个 view 获取和准备数据,它会把这项工作交给 view-model。 View controller 在创建的时候会一并创建 view-model,并且将每个 view 绑定到 view-model 所暴露出的相应属性上去。
    2. 更改 Model
      在 MVVM 中,view controller 接收 view 事件的方式和 MVC 中一样 (在 view 和 view controller 之间建立连接的方式也相同)。不过,当一个 view 事件到达时,view controller 不会 去改变自身的内部状态、view state、或者是 model。相对地,它立即调用 view-model 上的方 法,再由 view-model 改变内部状态或者 model。
    3. 更改 View
      和 MVC 不同,view controller 不监听 model。View-model 将负责观察 model,并将 model 的通知转变为 view controller 可以理解的形式。View controller 订阅 view-model 的变更,这 通常通过一个响应式编程框架来完成,但也可以使用任意其他的观察机制。当一个 view-model 事件来到时,由 view controller 去更改 view 层级。
      为了实现单向数据流,view-model 总是应该将变更 model 的 view action 发送给 model,并 且仅仅在 model 变化实际发生之后再通知相关的观察者。
    4. View State
      View state 要么存在于 view 自身之中,要么存在于 view-model 里。和 MVC 不同,view controller 中不存在任何 view state。View-model 中的 view state 的变更,会被 controller 观 察到,不过 controller 无法区分 model 的通知和 view state 变更的通知。当使用协调器时, view controller 层级将由协调器进行管理。
    5. 测试
      因为 view-model 和 view 层与 controller 层是解耦合的,所以可以使用接口测试来测试 view-model,而不需要像 MVC 里那样使用集成测试。接口测试要比集成测试简单得多,因为 不需要为它们建立完整的组件层次结构。
      为了让接口测试尽可能覆盖更多的范围,view controller 应当尽可能简单,但是那些没有被移 出 view controller 的部分仍然需要单独进行测试。在我们的实现中,这部分内容包括与协调器 的交互,以及初始时负责创建工作的代码。
    • MVVM 的重要性

    MVVM 是 iOS 上最流行的 MVC 的非直接变形的 app 设计模式。换言之,它和 MVC 相比,并没有非常大的不同;两者都是围绕 view controller 场景构建的,而且所使用的机制也大都相同。

    最大的区别可能在于 view-model 中对响应式编程的使用了,它被用来描述一系列的转换和依 赖关系。通过使用响应式编程来清晰地描述 model 对象与显示值之间的关系,为我们从总体上 理解应用中的依赖关系提供了重要的指导。

    iOS 中的协调器是一种很有用的模式,因为管理 view controller 层级是一件非常重要的事情。 协调器在本质上并没有和 MVVM 绑定,它也能被使用在 MVC 或者其他模式上。

    • 历史

    MVVM 由 Ken Cooper 和 Ted Peters 提出,他们当时在微软工作,负责后来变成 Windows Presentation Foundation (WPF) 的项目,这是微软.NET 的 app 框架,并于 2005 年正式发布。

    WPF 使用一种基于 XML,被称为 XAML 的描述性语言来描述 view 所绑定的某个 view-model 上的属性。在 Cocoa 中,没有 XAML,我们必须使用像是 RxSwift 这样的框架和一些 (通常存 在于 controller 中的) 代码来完成 view-model 和 view 的绑定。

    MVVM 和我们在 MVC 历史中提到的 MVP 模式非常类似. 不过,在 Cooper 和 Peters 的论述中, MVVM 中 view 和 view-model 的绑定需要明确的框架支持,但 presenter 是通过传统的手动 方式来传递变化。

    iOS 中的协调器则是最近才 (重新) 流行起来的,Soroush Khanlou 在 2015 年时在他的网站上描述了这个想法。协调器基于 app controller 这样的更古老的模式,而它们在 Cocoa 和其他平台上已经存在了有数十年之久。

    3. Model-View-Controller+ViewState

    MVC+VS 是为标准的 MVC 带来单向数据流方式的一种尝试。在标准的 Cocoa MVC 中,view state 可以由两到三种不同的路径进行操作,MVC+VS 则试图避免这点,让 view state 的处理 更加易于管理。在 MVC+VS 中,我们明确地在一个新的 model 对象中,对所有的 view state 进行定义和表达,我们把这个对象叫做 view state model。

    在 MVC+VS 中,我们不会忽略任何一次导航变更,列表选择,文本框编辑,开关变更,model 展示或者滚动位置变更 (或者其他任意的 view state 变化)。我们将这些变更发送给 view state model。每个 view controller 负责监听 view state model,这样变更的通讯会非常直接。在表现或者交互逻辑部分,我们不从 view 中去读取 view state ,而是从 view state model 中去获 取它们:

    在这里插入图片描述

    结果所得到的图表和 MVC 类似,但 controller 的内部反馈回路的部分 (被用来更新 view state) 有所不同,现在它和 model 的回路类似,形成了一个独立的 view state 回路。

    1. 构建
      和传统的 MVC 一样,将文档 model 数据应用到 view 上的工作依然是 view controller 的责任, view controller 还会使用和订阅 view state 。因为 view state model 和文档 model 都需要观 察,所以相比于典型的 MVC 来说,我们需要多得多的通过通知进行观察的函数。
    2. 更改 Model
      当 view action 发生时,view controller 去变更文档 model (这和 MVC 保持不变) 或者变更 model state。我们不会去直接改变 view 层级,所有的 view 变更都要通过文档 model 和 view state model 的通知来进行。
    3. 更改 View
      Controller 同时对文档 model 和 view state model 进行观察,并且只在变更发生的时候更新 view 层级。
    4. View State
      View State 被明确地从 view controller 中提取出来。处理的方法和 model 是一样的: controller 观察 view state model,并且对应地更改 view 层级。
    5. 测试
      在 MVC+VS 中,我们使用和 MVC 里类似的集成测试,但是测试本身会非常不同。所有的测试 都从一个空的根 view controller 开始,然后通过设定文档 model 和 view state model,这个 根 view controller 可以构建出整个 view 层级和 view controller 层级。MVC 的集成测试中最困 难的部分 (设定所有的部件) 在 MVC+VS 中可以被自动完成。要测试另一个 view state 时,我 们可以重新设置全局 view state,所有的 view controller 都会调整自身。
      一旦 view 层级被构建,我们可以编写两种测试。第一种测试负责检查 view 层级是不是按照我 们的期望被建立起来,第二种测试检查 view action 有没有正确地改变 view state。
    • MVC+VS 的重要性

    MVC+VS 主要是用来对 view state 进行教学的工具。
    在一个非标准 MVC 的 app 中,添加一个 view state model,并且在每个 view controller 中 (在已经对 model 进行观察的基础上) 观察这些 view state model,提供了不少优点:任意的状 态恢复 (这种恢复不依赖于 storyboard 或者 UIStateRestoration),完整的用戶界面日志,以及 为了调试目的,在不同的 view state 间进行跳转的能力。

    • 历史

    这种特定的体系是 Matt Gallagher 在 2017 年开发的教学工具,它被用来展示单向数据流和用 戶界面的时间旅行等概念。这个模式的目标是,在传统的 Cocoa MVC app 上通过最小的改动, 实现对 view 的状态在每个 action 发生时都可以进行快照。

    4. Model 适配器-View 绑定器 (MAVB)

    MAVB 是一种以绑定为中心的实验模式。在这个模式中,有三个重要的概念:view 绑定器, model 适配器,以及绑定。

    View 绑定器是 view (或者 view controller) 的封装类:它构建 view,并且为它暴露出一个绑定 列表。一些绑定为 view 提供数据 (比如,一个标签的文本),另一些从 view 中发出事件 (比如, 按钮点击或者导航变更)。

    虽然 view 绑定器可以含有动态绑定,但是 view 绑定器本身是不可变的。这让 MAVB 也成为了 一种声明式的模式:你声明 view 绑定器和它们的 action,而不是随着时间去改变 view 绑定器。

    Model 适配器是可变状态的封装,它是由所谓的 reducer 进行实现的。Model 适配器提供了一 个 (用于发送事件的) 输入绑定,以及一个 (用于接收更新的) 输出绑定。

    在 MAVB 中,你不会去直接创建 view;相对地,你只会去创建 view 绑定器。同样地,你也从 来不会去处理 model 适配器以外的可变状态。在 view 绑定器和 model 适配器之间的 (两个方 向上的) 变换,是通过 (使用标准的响应式编程技术) 来对绑定进行变形而完成的。

    MAVB 移除了对 controller 层的需求。创建逻辑通过 view 绑定器来表达,变换逻辑通过绑定来 表达,而状态变更则通过 model 适配器来表达。结果得到的框图如下:

    在这里插入图片描述

    1. 构建
      Model 适配器 (用来封装主 model ) 和 view state 适配器 (封装顶层的 view state) 通常是在
      main.swift 文件中进行创建的,这早于任何的 view。
      View 绑定器使用普通的函数进行构建,这些函数接受必要的 model 适配器作为参数。实际的
      Cocoa view 则由框架负责进行创建。 2. 更改 Model
      当一个 view (或者 view controller) 可以发出 action 时,对应的 view 绑定允许我们指定一个 action 绑定。在这里,数据从 view 流向 action 绑定的输出端。典型情况下,输出端会与一个 model 适配器相连接,view 事件会通过绑定进行变形,成为 model 适配器可以理解的一条消 息。这条消息随后被 model 适配器的 reducer 使用,并改变状态。
    2. 更改 View
      当 model 适配器的状态发生改变时,它会通过输出信号产生通知。在 view 绑定器中,我们可 以将 model 适配器的输出信号进行变形,并将它绑定到一个 view 属性上去。这样一来,view 属性就会在一个通知被发送时自动进行变更了。
    3. View State
      View state 被认为是 model 层的一部分。View state action 以及 view state 通知和 model action 以及 model 通知享有同样的路径。
    4. 测试
      在 MAVB 中,我们通过测试 view 绑定器来测试代码。由于 view 绑定器是一组绑定的列表,我 们可以验证绑定包含了我们所期望的条目,而且它们的配置正确无误。我们可以和使用绑定来 测试初始构建以及发生变化时的情况。
      在 MAVB 中进行的测试,与在 MVVM 中的测试很相似。不过,在 MVVM 中,view controller 有可能会包含逻辑,这导致在 view-model 和 view 之间有可能会存在没有测试到的代码。而 MAVB 中不存在 view controller,绑定代码是 model 适配器和 view 绑定器之间的唯一的代码, 这样一来,保证完整的测试覆盖要简单得多。
    • MAVB 的重要性
      在我们所讨论的主要模式之中,MAVB 没有遵循某个直接的先例,它既不是从其他平台移植过 来的模式,也不是其他模式的变种。它自成一派,用于试验目的,而且一些奇怪。我们在这儿 介绍它的意义在于,它展示了一些很不一样的东西。不过,这并不是说这个模式没有从其他模 式中借鉴经验教训:像是绑定、响应式编程、领域专用语言以及 reducer 都是已经被熟知的想 法了。
    • 历史
      MAVB 是 Matt Gallagher 在 Cocoa with Love 网站上首先提出的。这个模式参照了 Cocoa 绑 定、函数式响应动画、ComponentKit、XAML、Redux 以及成千上万行的使用 Cocoa view controller 的经验。
      本书中的实现使用了 CwlViews 框架来处理 view 构建、绑定器和适配器的实现等工作。

    5. Elm 架构 (TEA)

    TEA 和 MVC 有着根本上的不同。在 TEA 中,model 和所有的 view state 被集成为一个单个状 态对象,所有 app 中的变化都通过向状态对象发送消息来发生,一个叫做 reducer 的状态更新 函数负责处理这些消息。
    在 TEA 中,每个状态的改变会生成一个新的虚拟 view 层级,它由轻量级的结构体组成,描述 了 view 层级应该看上去的形式。虚拟 view 层级让我们能够使用纯函数的方式来写 view 部分 的代码;虚拟 view 层级总是直接从状态进行计算,中间不会有任何副作用。当状态发生改变 时,我们使用同样的函数重新计算 view 层级,而不是直接去改变 view 层级。
    Driver 类型 (这是 TEA 框架中的一部分,它负责持有对 TEA 中其他层的引用) 将对虚拟 view 层 级和 UIView 层级进行比较,并且对它进行必要的更改,让 view 和它们的虚拟版本相符合。这 个 TEA 框架中的 driver (驱动) 部件是随着我们 app 的启动而被初始化的,它自身并不知道要对 应哪个特定的 app。我们要在它的初始化方法中传入这些信息:包括 app 的初始状态,一个通 过消息更新状态的函数,一个根据给定状态渲染虚拟 view 层级的函数,以及一个根据给定状态 计算通知订阅的函数 (比如,我们可以订阅某个 model store 更改时所发出的通知)。
    从框架的使用者的视⻆来看,TEA 的关于更改部分的框图是这样的:

    在这里插入图片描述
    如果我们追踪这张图表的上面两层,我们会发现在 view 和 model 之间存在我们在本章开头是 就说过的反馈回路;这是一个从 view 到状态,然后再返回 view 的回路 (通过 TEA 框架进行协 调)。
    下面的回路代表的是 TEA中处理副作用的方式 (比如将数据写入磁盘中):当在状态更新方法中 处理消息时,我们可以返回一个命令,这些命令会被 driver 所执行。在我们的例子中,最重要 的命令是更改 store 中的内容,store 反过来又被 driver 所持有的订阅者监听。这些订阅者可 以触发消息来改变状态,状态最终触发 view 的重新渲染作为响应。
    这些事件回路的结构让 TEA 成为了遵守单向数据流原则的设计模式的另一个例子。

    1. 构建
      状态在启动时被构建,并传递给运行时系统 (也就是 driver)。运行时系统拥有状态,store 是一 个单例。
      初始的 view 层级和之后更新时的 view 层级是通过同样的路径构建的:通过当前的状态,计算 出虚拟 view 层级,运行时系统负责更新真实的 view 层级,让它与虚拟 view 层级相匹配。
    2. 更改 Model
      虚拟 view 拥有与它们所关联的消息,这些消息在一个 view 事件发生时会被发送。Driver 可以 接收这些消息,并使用更新方法来改变状态。更新方法可以返回一个命令 (副作用),比如我们
      想在 store 中进行的改动。Driver 会截获该命令并执行它。TEA 让 view 不可能直接对状态或者 store 进行更改。
    3. 更改 View
      运行时系统负责这件事。改变 view 的唯一方式是改变状态。所以,初始化创建 view 层级和更
      新 view 层级之间没有区别。 4. View State
      View state 是包含在整体的状态之中的。由于 view 是直接从状态中计算出来的,导航和交互状 态也同样会被自动更新。
    4. 测试
      在大多数架构中,让测试部件彼此相连往往要花费大量努力。在 TEA 中,我们不需要对此进行 测试,因为 driver 会自动处理这部分内容。类似地,我们不需要测试当状态变化时 view 会正确 随之变化。我们所需要测试的仅仅是对于给定的状态,虚拟 view 层级可以被正确计算。
      要测试状态的变更,我们可以创建一个给定的状态,然后使用 update 方法和对应的消息来改 变状态。然后通过对比之前和之后的状态,我们就可以验证 update 是否对给定的状态和消息 返回了所期望的结果。在 TEA 中,我们还可以测试对应给定状态的订阅是不是正确。和 view 层级一样,update 函数和订阅也都是纯函数。
      因为所有的部件 (计算虚拟 view 层级,更新函数和订阅) 都是纯函数,我们可以对它们进行完 全隔离的测试。任何框架部件的初始化都是不需要的,我们只用将参数传递进去,然后验证结 果就行了。我们 TEA 实现中的大多数测试都非常直截了当。
    • Elm 架构的重要性
      TEA 最早是在 Elm 这⻔函数式语言中被实现的。所以 TEA 是一种如何用函数式的方法表达 GUI 编程的尝试。TEA 同时也是最为古老的单向数据流架构。

    • 历史
      Elm 是 Evan Czaplicki 所设计的函数式编程语言,它最初的目的是为了构建前端 web app。 TEA 是归功于 Elm 社区的一个模式,它的出现是语言约束和目标环境相互作用的自然结果。它 背后的思想影响了很多其他的基于 web 的框架,其中包括 React、Redux 和 Flux 等。在 Swift 中,还没有 TEA 的权威实现,不过我们可以找到不少研究型的项目。在本书中,我们使用 Swift 按我们自己的理解实现了这个模式。主要的工作由 Chris Eidhof 于 2017 年完成。虽然我 们的这个实现还并不是 “产品级” 的,但是许多想法是可以用在生产代码中的。

    三,其他APP架构模式

    1. Model-View-Presenter

    Model-View-Presenter (MVP) 是一种在 Android 上很流行的模式,在 iOS 中,也有相应的实 现。在总体结构和使用的技术上,它粗略来说是一种位于标准 MVC 和 MVVM 之间的模式。

    MVP 使用单独的 presenter 对象,它和 MVVM 中 view-model 所扮演的⻆色一样。相对 view-model 而言,presenter 去除了响应式编程的部分,而是把要展示的值暴露为接口上的属 性。不过,每当这些值需要变更的时候,presenter 会立即将它们推送到下面的 view 中去 (view 将自己作为协议暴露给 presenter)。
    从抽象的观点来看,MVP 和 MVC 很像。Cocoa 的 MVC,除了名字以外,就是一个 MVP - 它是 从上世纪九十年代 Taligent 的原始的 MVP 实现中派生出来的。View,状态和关联的逻辑在两 个模式中都是一样的。不同之处在于,现代的 MVP 中有一个分离的 presenter 实体,它使用协 议来在 presenter 和 view controller 之间进行界定,Cocoa 的 MVC 让 controller 能够直接引 用 view,而 MVP 中的 presenter 只能知道 view 的协议。

    有些开发者认为协议的分离对于测试是必要的。当我们在讨论测试时,我们会看到标准的 MVC 在没有任何分离的情况下,也可以被完整测试。所以,我们感觉 MVP 并没有太大不同。如果我 们对测试一个完全解耦的展示层有强烈需求的话,我们认为 MVVM 的方式更简单一些:让 view controller 通过观察去从 view-model 中拉取值,而不是让 presenter 将值推送到一个协 议中去。

    2. VIPER,Riblets,和其他 “Clean” 架构

    VIPER,Riblets 和其他类似的模式尝试将 Robert Martin 的 “Clean Architecture” 从 web app 带到 iOS 开发中,它们主要把 controller 的职责分散到三到四个不同的类中,并用严格的顺序 将它们排列起来。在序列中的每个类都不允许直接引用序列中前面的类。

    为了强制单方向的引用这一规则,这些模式需要非常多的协议,类,以及在不同层中传递数据 的方式。由于这个原因,很多使用这些模式的开发者会去使用代码生成器。我们的感觉是,这 些代码生成器,以及任何的繁杂到需要生成器的模式,都产生了一些误导。将 “Clean” 架构带 到 Cocoa 的尝试通常都宣称它们可以管理 view controller 的 “肥大化” 问题,但是让人啼笑皆 非的是,这么做往往让代码库变得更大。

    虽然将接口分解是控制代码尺寸的一种有效手段,但是我们认为这应该按需进行,而不是教条 式地对每个 view controller 都这么操作。分解接口需要我们对数据以及所涉及到的任务有清楚 的认识,只有这样,我们才能达到最优的抽象,并在最大程度上降低代码的复杂度。

    3. 基于组件的架构 (React Native)

    如果你选择使用 JavaScript 而不是 Swift 编程,或者你的 app 重度依赖于 web API 的交互, JavaScript 会是更好的选择,这时你可能会考虑 React Native。不过,本书是专注于 Swift 和 Cocoa 的,所以我们将探索模式的界限定在了这些领域内。

    如果你想要找一些类似 React Native,但是是基于 Swift 的东西的话,可以看看我们对 TEA 的 探索。MAVB 的实现也从 ComponentKit 中获得了一些启发,而 ComponentKit 本身又从 React 中获取灵感:它使用类 DSL 的语法来进行声明式和可变形的 view 构建,这和 React 中 Component 的 render 方法及其相似。

    展开全文
  • 详解互联网APP架构1.0

    千次阅读 2018-09-11 20:44:33
    详解互联网APP架构1.0 详解互联网APP架构2.0 由于最近负责一个互联网APP项目,需要重新设计架构,这边架构已经设计完成,跟小伙伴们分享下设计思想: 首先我们分析大概的需求,可归结为以下几点: 此项目为...
  • 详解互联网APP架构2.0

    2019-11-05 19:23:04
    详解互联网APP架构1.0 详解互联网APP架构2.0 经过几个月业务的沉淀,明确出几个具体的业务方向,原本的架构已经不适合现在的项目。 下面从几个方向介绍我们的切换思路: 1、原本架构存在几个问题 网关层:...
  • 三种app架构设计图

    热门讨论 2014-05-27 01:19:13
    三种app架构设计图
  • 业内移动App架构参考

    千次阅读 2017-08-10 16:37:58
    业内移动App架构参考近一两年,业内一些移动应用都分享了各自的架构,Android官方也给出过一些架构示例,如MVP、Android Architecture Components等。以下是一些不错的移动架构分享的汇总,方便大家学习查看(排名不...
  • 移动App架构设计

    千次阅读 2017-05-02 10:04:27
    移动App架构设计 本文主要总结了几种常用的架构模式, 基本是层层递进的 转载请注名出处 http://blog.csdn.net/uxyheaven Native app的开发相比传统的项目迭代周期要短很多, 需求的变化也频繁一些, 在开发的...
  • 务实的iOS App架构

    2018-04-04 11:40:41
    关于iOS App架构的文章有很多,解决方案有很多,其中也不乏设计精巧的架构。但是没有一种架构适合所有的场景。But,如何选择、取舍呢?
  • Hybrid APP架构设计思路

    千次阅读 2016-11-15 11:01:49
    APP架构设计思路 关于Hybrid模式开发app的好处,网络上已有很多文章阐述了,这里不展开。 作为一种跨语言开发模式,通讯层是Hybrid架构首先应该考虑和设计的,往后所有的逻辑都是基于通讯层展开。 Native(以...
  • Android App架构设计

    千次阅读 2016-04-08 14:35:03
    我结合以前的工作和现在的工作,整理了下目前能想到的最好的Android App架构设计,在这里记录一下,以便以后用。 如图所示,为什么需要这样的架构? 现在的公司有的能同时进行好几个App的开发,那么这些App开发的...
  • APP架构设计经验谈:技术选型原创文章,转载请注明:转载自Keegan小钢并标明原文链接:...技术选型App架构设计经验谈:数据层的设计App架构设计经验谈:业务层的设计App架构设计经验谈:展示层...
  • App架构设计经验谈:接口的设计 App架构设计经验谈:技术选型 App架构设计经验谈:数据层的设计 App架构设计经验谈:业务层的设计 App架构设计经验谈:展示层的设计当你做架构设计时,必然会面临技术选型的抉择,不同...
  • Watch App架构和生命周期、WKInterfaceController的生命周期、WatchOS与 iOS的共性技术
  • APP架构构思基本思路初稿

    千次阅读 2016-01-10 23:26:49
    于是,鉴于此,我在app架构的设计上抽取出一个类库(MDLib)“妈蛋类库”。 先上图(MDLib):由图可知,Android和iOS的类库在这里采取了相同的架构。 第三方库集成引用虽然iOS有pods,Android有gradle,但是有时...
  • Android架构组件-App架构指南

    万次阅读 多人点赞 2017-11-08 00:55:21
    与传统的桌面应用程序不同,Android...一个典型的Android应用程序是由多个 app组件(Android四大组件) 构成的,包括 **activities**, **fragments**, **services**, **content providers** and **broadcast receivers
  • 安卓实战之如何快速搭建app架构

    万次阅读 2016-05-23 22:44:56
    前言最近公司的另一个项目又要立项了,作为公司的唯一安卓工程师任务来了(新来的移动端的老大说...如何选择app架构(MVC/MVP/MVVM)最近越来越多的人开始谈论架构。我周围的同事和工程师也是如此。尽管我还不是特别深
  • 接上篇《APP架构的那点事儿[网络模块]》完成了网络模块,那么我们是不是该进入MVP主体架构的开发了,当然,有些朋友会说:“难道一定要先写网络模块,就不能先做架构主体么!” 这个随意了,看个人喜好,有话好好说嘛,...
  • Android APP架构思考

    千次阅读 多人点赞 2017-05-21 09:16:02
    从2011年到现在,做了几年的Android应用与Android平台上Opengl es应用开发,下面是关于Android APP开发架构的一些思考:构建框架的最终目的是增强项目代码的可读性 ,维护性 和方便测试 ,如果背离了这个初衷,为了...
  • 携程移动App架构优化之旅

    千次阅读 2016-04-26 12:33:36
    通过本文,你可以了解携程通过哪些手段来优化它的App架构的。  『携程旅行App』作为携程超级App产品,是公司全品类旅行产品的核心售卖入口,过去两年为了更好支撑无线业务的快速发展,携程移动App在产品和技术...
  • App架构设计经验谈:接口的设计

    千次阅读 2016-02-29 15:17:19
    创文章,转载请注明:转载自Keegan小钢 ... 微信订阅号:keeganlee_me 写于2016-01-07 App架构设计经验谈:接口的设计 ...App架构设计经验谈:技术选型 ...App架构设计经验谈:数据层的设计 ...App架构设计经验谈:展
  • Android APP架构设计——MVP的使用示例

    万次阅读 2016-10-25 20:42:15
    对于这三种架构设计以及优缺点已经在Android APP架构设计——MVC、MVP和MVVM介绍一文中介绍过了,本文是对前面那篇文章2.3小节的补充,介绍MVP模式在Android中的使用示例,目的在于深化对MVP架构的理解。
  • Android APP架构的那点事儿 [开篇]

    千次阅读 2016-02-25 18:01:33
    那么问题就来,很多新人都会抱怨说我缺少一个完整项目的经验、要学习App架构巴拉巴拉。。。成!那今天我们就扒一扒APP架构的那些事。架构很重要?真的,很重要!很重要!很重要!说三遍! 早期的架构设计抉择对app的...
  • Android App 架构设计

    万次阅读 2017-07-21 16:44:13
    原文链接此文档写给希望学习最优编程实践和架构以开发健壮、高质量APP的开发者。开发者常遇到的问题传统的桌面程序大多数使用场景是有一个启动入口,作为一个独立进程运行。Android app结构要复杂很多,一个典型的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 295,850
精华内容 118,340
关键字:

app架构