写点什么

iOS 面试策略之经验之谈 - 架构的选择

用户头像
iOSer
关注
发布于: 2021 年 05 月 26 日
iOS 面试策略之经验之谈-架构的选择

这是本系列最后一个章节,主要是一些进阶内容的提问和解答,考察的是开发者功力的深厚


之前说一个 iOS 开发者成长到一定阶段,就会遇到瓶颈,解决的方法是熟悉设计模式。接触到 App 的架构 App 的架构就类似于现代建筑的脚手架或是地基——一旦确定,App 的骨架和结构就已经定型,剩下的工作就是在现成的架构中舔砖加瓦。那么具体来说,我们为什么要关心 App 的架构?有三点原因。


首先就是代码均摊。试想如果所有代码都集中在一个 UIViewController 中,App 理论上确实能够运行,然而当调试时面对拥有庞大代码的单个文件,我们需要花大量的时间去找到发生问题的源头。同时在修改代码的同时,又因为所有代码都集中在一处,我们必须格外小心,防止一处修改、他处崩溃这种牵一发而动全身的情况出现。这种就像很多团电路交错在一起,即使是熟练的电工也因为过于复杂而觉得无从下手。真正的架构应该合理分配代码,每个类、结构体、方法、变量的存在都应该遵循单一职责原则。


其次是便于测试。测试确保了代码的质量。我们熟知的单元测试、性能测试、UI 测试都是针对单个方法或界面进行测试。架构的合理分配决定了各个测试能够各司其职,不重复、不遗漏,做到最大的测试效率和覆盖率。


最后就是易用性。好的架构确保了日后开发中可以轻松应对各种新需求;即使是新人也可以快速学习并适应现有的架构并进行开发。


本节将围绕目前流行的 MVC,MVP,MVCS,MVVM,VIPER 等架构来展开。由于绝大多数开发者对于部分架构并不熟悉,本节将着重对架构进行特点分析,并在其之间进行横向比较。

1.说说苹果官方的 MVC 架构的优缺点?

关键词:#耦合


MVC 的优点有 2 个:


  • **代码总量少。**基本上大量的逻辑和视图代码都集中在 ViewController 里,View 和 Model 也严格区分,代码分配遵循一定规则。

  • **简单易懂。**新人可以快速上手;修改和增加新的功能也没有明显障碍;即使是没有经验的开发者也可以很好维护。


缺点主要由视图层 和控制器层高度耦合造成,其负面影响主要为:


  • **代码过于集中。**ViewController 因为将两部分高度耦合,它将处理交互、视图更新、布局、Model 数据获取和修改、导航等几乎所有操作。

  • **难以进行测试。**由于高度耦合,使得用于检测功能为主的单元测试需要配合特定视图才能进行,测试难度陡增。所以经常在 MVC 架构中,开发者一般只对 Model 进行测试。

  • **难以扩展。**在 ViewController 里添加新功能需要格外小心,高度耦合的逻辑结构增加了出错的风险;同时由于 View 和 Controller 部分由于互相依赖,增加新功能不仅可能需要大量修改原有代码,也会使 ViewController 愈发笨重。

  • **Model 层过于简单。**相比于 ViewController 的庞大代码,Model 层只是定义几个属性。在 Objective-C 的 “.m” 实现文件中,更是几乎看不到代码。

  • **网络请求逻辑无从安放。**网络层放在 Model 中,其异步调用的 API 请求会使得整个 Model 层变得复杂。若是将网络层 放在 ViewController 中,则耦合进一步加剧,以上缺点更加放大。


其实 MVC 的缺点一言以蔽之,就是过于笼统的代码分配。任何一个类或者结构体,只要不是数据或是视图,就被放在了控制器一层,而 ViewController 类耦合了视图和控制器,可以说这是 MVC 架构天生的缺点。


如果你正在跳槽或者正准备跳槽不妨动动小手,添加一下咱们的交流群101 295 1431来获取一份详细的大厂面试资料为你的跳槽多添一份保障。

2.代码实战:以下代码实现的 MVC 架构有什么缺点?

关键词:#view #model


class User {  var name: String  var avatar: UIImage
init(_ name: String, _ avatar: UIImage) { self.name = name self.avatar = avatar }}
extension UIImageView { func configure(with user: User) { ... }}
class ViewController: UIViewController { var user: User? var userImageView: UIImageView?
override func viewDidLoad() { super.viewDidLoad()
userImageView = UIImageView() userImageView.configure(with: user) }}
复制代码


以上代码是经典的 MVC 架构,然而却在两个地方将 View 和 Model 层耦合在了一起。


首先,User 类作为 Model,其内部是不应该有 UIImage 这种视图属性的,可以将其改为 NSData。


其次,userImageView 作为 View 层,是不应该与 Model 层直接接触的。而在 viewDidLoad 中,我们却发现 userImageView 直接可以调用做为 Model 的 User 去进行配置。这个操作应该由 ViewController 去完成


修改后的代码如下:class User {  var name: String  var avatarData: Data
init(_ name: String, _ avatarData: Data) { self.name = name self.avatarData = avatarData }}
class ViewController: UIViewController { var user: User? var userImageView: UIImageView?
override func viewDidLoad() { super.viewDidLoad()
userImageView = UIImageView() configure(userImageView, with: user) }
func configure(_ imageView: UIImageView?, with user: User) { ... }}
复制代码

3.MVCS 中的 S 为什么要单独拆分出来?

关键词:#数据层 #网络层


MVCS 架构其实就是针对 MVC 的优化。S 是 Store 的缩写,意为存储。一般数据持续化层(例如 Core Data )就是 Store,我们把这部分代码单独从 Model 或是 ViewController 里拆分出来构成单独的文件,这就是所谓的数据层。


之前我们提到,MVC 的缺点之一就是网络层无处安放。其实根据 MVCS 这个思路,我们我们也可以把网络层放在 S 这一层中。毕竟网络请求也是获得数据,而且一般 API 请求之后数据都要做缓存和持久化处理,所以放在 S(数据层)来说也比较合理。


拆分出来之后,整个代码分配更加均衡。同时以往在 ViewController 里面难以进行的单元测试也可以根据单独的数据层文件进行测试,总体来讲测试覆盖率会有所提高。整个拆分之后对于整体架构的维护和扩展也起到了促进作用。

4.说说 MVP 和 MVC 相比有什么异同?

关键词:#解耦 #代码量


MVP 的全称是 Model-View-Presenter。它和 MVC 的相同点在于:两者的 Model 功能一样,理论上来讲两者的 Model 层应该完全一样。


而不同点在于,MVC 中 View 和 Controller 耦合在 ViewController 类里;而 MVP 的 View 是单独的 UIView/UIViewController,Presenter 也是单独的类。我们来看下 MVP 的结构:



如图,MVP 中的 View 是单独的 Class(在 MVP 中,UIView 或是 UIViewController 都属于 View 层),它持有 Presenter 作为变量。当接收到用户交互时,它会调用 Presenter 进行处理。也就是说,View 层不包含任何的业务逻辑代码,它只会将交互交给 Presenter,并从 Presenter 那里接受结果来更新自己。


如果你正在跳槽或者正准备跳槽不妨动动小手,添加一下咱们的交流群101 295 1431来获取一份详细的大厂面试资料为你的跳槽多添一份保障。


而 MVP 中的 Presenter 则负责业务逻辑,它是 View 和 Model 的桥接。它会根据 View 中的交互去修改 Model,或根据 Model 的变化去修改 View。


这里要注意,因为 View 持有 Presenter,所以 Presenter 中的 View 应该声明为 weak 或 unowned,以避免循环引用。


相比于 MVC,MVP 的耦合度大大降低,代码分配更加合理,测试起来非常方便,整个架构理解和上手难度也并不高。


但是它的缺点在于,View 的所有交互都要传给 Presenter 去处理,这样就项目功能一旦增加,View 的代码和 Presenter 的代码都会增加。相比于 MVC 在 ViewController 一个文件里面直接解决,MVP 的总代码量可能会翻倍,这样 App 的维护成本和文件大小都会增大。

5.MVVM 中的 ViewModel 的作用是什么?

关键词:#数据提供 #交互响应


ViewModel 一般来扮演两个重要角色:


  • **视图层的真正数据提供者。**一般视图层展示的数据经常是当个或是多个模型的属性组合。例如微博数据流界面,可能一个微博用户模型有 firstName, lastName, status, post 多个属性,ViewModel 就会将这些数据整合在一起,使得视图可以直接调用单个数据就展示所要的效果。简单来说,ViewModel 就是为了视图展示,而对模型层的数据包装。

  • **视图层的交互响应者。**所有用户的交互都会传递给 ViewModel,ViewModel 会依次更新视图层需要的属性,同时相应修改模型层的数据。这里依靠的是属性观察或响应式架构。


注意 ViewModel 类中绝对不能包含视图层的任何类或结构体。MVVM 的示意图如下:



6. 试比较 MVC,MVP,MVVM 三种架构。

关键词:#模型层 #中间层 #视图层


MVC、MVP、MVVM 三种架构皆由模型层(M - Model),视图层(V - View),中间层(C/P/VM - Controller/Presenter/View Model)构成。我们的比较就先从局部开始——分别比较这三个部分,再到整体差异。


  • **模型层几乎相同。**三种架构的模型理论来说都是数据来源,没有什么不同。

  • **视图层理论上都设计为被动,但是实际上略有不同。**实际开发中 MVC 中视图层与中间层高度耦合,几乎所有的操作都统一由 ViewController 包办。

  • 但理论上来说,MVC 是希望视图层就是单纯的 UIView,或者 UIViewController 只负责 UI 更新交互,不涉及业务逻辑和模型更新。

  • MVP 和 MVVM 在实际开发中视图层实现了 MVC 理论期望,即与中间层严格分离。

  • MVP 中视图层是完全被动,单纯的把交互和更新传递给中间层;而 MVVM 中视图层并不是完全被动——它会监视中间层的变化,一旦产生变化,则视图层也会相应变化。

  • **中间层的设计是三种架构的核心的差异。**逻辑上讲,中间层的作用就是连接视图层和模型层。它处理交互、接受通知、完成数据更新。

  • MVC 的中间层 Controller 持有视图和模型,主要起到一个组装和连接的作用,通过传递参数和实例变量来直接完成所有操作。

  • MVP 的中间层 Presenter 持有模型,在更新模型上与 MVC 的 Controller 角色一样。但它不拥有视图,视图拥有中间层,中间层的工作流程是:从视图层接收交互传递->响应->向视图层传递响应指令->视图进行更新。全部操作必须手动书写代码完成。

  • MVVM 的中间层 View Model 持有模型,在更新模型上与前两者相同。它完全独立于视图,视图拥有中间层,通过绑定属性,自动进行更新。全部操作由响应式逻辑框架自动完成。

  • **MVC 耦合度很高,代码分配最不合理,维护和扩展成本最高。**但因为无需层级传递,所以代码总量最少,适合初学者理解和应用。

  • **MVP 和 MVVM 相似,耦合度和代码分配都比较合理,较易实现高测试覆盖率。**MVP 的缺点是视图层需要将所有的交互传递给中间层,且要手动实现响应和更新,所以总代码量远超 MVVM。MVVM 在响应和更新上通过响应式框架自动操作,大大精简了代码量;但是需要引入第三方响应式框架,同时因为属性观察环环相扣,调用栈很大,debug 起来尤为痛苦。

  • **MVC,MVP,MVVM 这三种结构都是以视图为驱动的架构,三种皆为用户交互和视图更新为主要服务目标。**它们一个共同的缺点是没有涉及界面之间的跳转——即路由的设计。

7. VIPER 之间的各个组件是如何交互的?

关键词:#路由 #Interactor


VIPER 架构分别由 5 部分组成:View, Interactor, Presenter, Entity, Router。它的示意图如下,我们从左向右依次来看:



  • **视图层(View)。**与 MVP 或者 MVVM 的视图层类似。它包含与 UI 相关的一切操作。它接收用户的交互信息单并不处理,而是传递给展示层(Presenter)。

  • **展示层(Presenter)。**与 MVP 的 Presenter 或是 MVVM 的 ViewModel 功能类似,更像 Presenter 还是 ViewModel,取决于是否引入响应式编程框架。Presenter 这里只响应并处理视图层传来的交互操作请求,并不直接对数据源进行修改,这是与 MVX 中中间层最大的不同。若要修改数据,展示层会向其持有的数据管理层(Interactor)发送请求,Interactor 会处理一切有关数据源的操作。此外它还连接了路由层(Router)。

  • **路由层(Router)。**专门负责界面跳转和组件之间切换。当 App 较小时,Router 负责页面跳转。当 App 比较大时,不同功能和业务会拆分成不同模块或组件,Router 的作用就是在不同组件之间进行链接。这是之前 MVX 架构所忽略的部分。

  • **数据管理层(Interactor)。**专门负责处理数据源信息。包括网络请求、数据传输、缓存、存储、生成实例等操作。实际上之前中间层和模型层的一些逻辑被进一步剥离至此,整个架构的逻辑也显得更加清晰。

  • **模型层(Entity)。**只拥有初始化方法和属性相关 set/get 方法,与之前的 Model 大同小异。


由于分工明确,VIPER 层在代码分配、测试覆盖率上为所有架构之冠。


缺点在于,它依然与 MVX 架构一样,是个视图驱动的架构。同时,由于分工精细,不同层级之间交互的代码很多,总体代码量很大,不适宜用在小型 App 中。

## 文章到这里就结束了,感谢你的观看,只是有些话想对读者们说说:

iOS 开发人群越来越少,说实在的,每次在后台看到一些读者的回应都觉得很欣慰,至少你们依然坚守 iOS 技术岗…为了感谢读者们,我想把我收藏的一些编程干货贡献给大家,回馈每一个读者,希望能帮到你们。

干货主要有:

① iOS 中高级开发必看的热门书籍(经典必看)

② iOS 开发技术进阶教学视频

③ BAT 等各个大厂 iOS 面试真题+答案.PDF 文档

④ iOS 开发中高级面试"简历制作"指导视频

**如果你用得到的话可以直接拿走;如何获取,具体内容请转看-我的**[**GitHub**](https://github.com/iOS-Mayday/iOSAdvanced-Roadmap)

**我的:**[**GitHub 地址**](https://github.com/iOS-Mayday/iOSAdvanced-Roadmap)

用户头像

iOSer

关注

微信搜索添加微信 mayday1739 进微信群哦 2020.09.12 加入

更多大厂面试资料进企鹅群931542608

评论

发布
暂无评论
iOS 面试策略之经验之谈-架构的选择