在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ iOS/ 使用 VIPER 構(gòu)建 iOS 應(yīng)用
與四軸無(wú)人機(jī)的通訊
在沙盒中編寫(xiě)腳本
結(jié)構(gòu)體和值類(lèi)型
深入理解 CocoaPods
UICollectionView + UIKit 力學(xué)
NSString 與 Unicode
代碼簽名探析
測(cè)試
架構(gòu)
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅(qū)動(dòng)開(kāi)發(fā)
Collection View 動(dòng)畫(huà)
截圖測(cè)試
MVVM 介紹
使 Mac 應(yīng)用數(shù)據(jù)腳本化
一個(gè)完整的 Core Data 應(yīng)用
插件
字符串
為 iOS 建立 Travis CI
先進(jìn)的自動(dòng)布局工具箱
動(dòng)畫(huà)
為 iOS 7 重新設(shè)計(jì) App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網(wǎng)絡(luò)應(yīng)用實(shí)例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類(lèi)
與調(diào)試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動(dòng)畫(huà)解釋
響應(yīng)式 Android 應(yīng)用
初識(shí) TextKit
客戶(hù)端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調(diào)試
項(xiàng)目介紹
Swift 的強(qiáng)大之處
測(cè)試并發(fā)程序
Android 通知中心
調(diào)試:案例學(xué)習(xí)
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機(jī)制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學(xué)習(xí)的一代人
視頻
Playground 快速原型制作
Omni 內(nèi)部
同步數(shù)據(jù)
設(shè)計(jì)優(yōu)雅的移動(dòng)游戲
繪制像素到屏幕上
相機(jī)與照片
音頻 API 一覽
交互式動(dòng)畫(huà)
常見(jiàn)的后臺(tái)實(shí)踐
糟糕的測(cè)試
避免濫用單例
數(shù)據(jù)模型和模型對(duì)象
Core Data
字符串本地化
View Controller 轉(zhuǎn)場(chǎng)
照片框架
響應(yīng)式視圖
Square Register 中的擴(kuò)張
DTrace
基礎(chǔ)集合類(lèi)
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點(diǎn)互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設(shè)計(jì)的藝術(shù)
導(dǎo)航應(yīng)用
線程安全類(lèi)的設(shè)計(jì)
置換測(cè)試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無(wú)人機(jī)項(xiàng)目
Mach-O 可執(zhí)行文件
UI 測(cè)試
值對(duì)象
活動(dòng)追蹤
依賴(lài)注入
Swift
項(xiàng)目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務(wù)
自定義 Collection View 布局
測(cè)試 View Controllers
訪談
收據(jù)驗(yàn)證
數(shù)據(jù)同步
自定義 ViewController 容器轉(zhuǎn)場(chǎng)
游戲
調(diào)試核對(duì)清單
View Controller 容器
學(xué)無(wú)止境
XCTest 測(cè)試實(shí)戰(zhàn)
iOS 7
Layer 中自定義屬性的動(dòng)畫(huà)
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲(chǔ)
代碼審查的藝術(shù):Dropbox 的故事
GPU 加速下的圖像視覺(jué)
Artsy
照片擴(kuò)展
理解 Scroll Views
使用 VIPER 構(gòu)建 iOS 應(yīng)用
Android 中的 SQLite 數(shù)據(jù)庫(kù)支持
Fetch 請(qǐng)求
導(dǎo)入大數(shù)據(jù)集
iOS 開(kāi)發(fā)者的 Android 第一課
iOS 上的相機(jī)捕捉
語(yǔ)言標(biāo)簽
同步案例學(xué)習(xí)
依賴(lài)注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識(shí)別
玩轉(zhuǎn)字符串
相機(jī)工作原理
Build 過(guò)程

使用 VIPER 構(gòu)建 iOS 應(yīng)用

建筑領(lǐng)域流行這樣一句話,“我們雖然在營(yíng)造建筑,但建筑也會(huì)重新塑造我們”。正如所有開(kāi)發(fā)者最終領(lǐng)悟到的,這句話同樣適用于構(gòu)建軟件。

編寫(xiě)代碼中至關(guān)重要的是,需要使每一部分容易被識(shí)別,賦有一個(gè)特定而明顯的目的,并與其他部分在邏輯關(guān)系中完美契合。這就是我們所說(shuō)的軟件架構(gòu)。好的架構(gòu)不僅讓一個(gè)產(chǎn)品成功投入使用,還可以讓產(chǎn)品具有可維護(hù)性,并讓人不斷頭腦清醒的對(duì)它進(jìn)行維護(hù)!

在這篇文章中,我們介紹了一種稱(chēng)之為 VIPER 的 iOS 應(yīng)用架構(gòu)的方式。VIPER 已經(jīng)在很多大型的項(xiàng)目上成功實(shí)踐,但是出于本文的目的我們將通過(guò)一個(gè)待辦事項(xiàng)清單 (to-do app) 來(lái)介紹 VIPER 。你可以在 GitHub 上關(guān)注這個(gè)項(xiàng)目。

什么是 VIPER?

測(cè)試永遠(yuǎn)不是構(gòu)建 iOS 應(yīng)用的主要部分。當(dāng)我們 (Mutual Mobile) 著手改善我們的測(cè)試實(shí)踐時(shí),我們發(fā)現(xiàn)給 iOS 應(yīng)用寫(xiě)測(cè)試代碼非常困難。因此如果想要設(shè)法改變測(cè)試的現(xiàn)狀,我們首先需要一個(gè)更好的方式來(lái)架構(gòu)應(yīng)用,我們稱(chēng)之為 VIPER。

VIPER 是一個(gè)創(chuàng)建 iOS 應(yīng)用簡(jiǎn)明構(gòu)架的程序。VIPER 可以是視圖 (View),交互器 (Interactor),展示器 (Presenter),實(shí)體 (Entity) 以及路由 (Routing) 的首字母縮寫(xiě)。簡(jiǎn)明架構(gòu)將一個(gè)應(yīng)用程序的邏輯結(jié)構(gòu)劃分為不同的責(zé)任層。這使得它更容易隔離依賴(lài)項(xiàng) (如數(shù)據(jù)庫(kù)),也更容易測(cè)試各層間的邊界處的交互:

http://wiki.jikexueyuan.com/project/objc/images/13-15.jpg" alt="" />

大部分 iOS 應(yīng)用利用 MVC 構(gòu)建,使用 MVC 應(yīng)用程序架構(gòu)可以引導(dǎo)你將每一個(gè)類(lèi)看做模型,視圖或控制器中的一個(gè)。但由于大部分應(yīng)用程序的邏輯不會(huì)存在于模型或視圖中,所以通常最終總是在控制器里實(shí)現(xiàn)。這就導(dǎo)致一個(gè)稱(chēng)為重量級(jí)視圖控制器的問(wèn)題,在這里,視圖控制器做了太多工作。為這些重量級(jí)視圖控制器瘦身并不是 iOS 開(kāi)發(fā)者尋求提高代碼的質(zhì)量所要面臨的唯一挑戰(zhàn),但至少這是一個(gè)很好的開(kāi)端。

VIPER 的不同層提供了明確的程序邏輯以及導(dǎo)航控制代碼來(lái)應(yīng)對(duì)這個(gè)挑戰(zhàn),利用 VIPER ,你會(huì)注意到在我們的待辦事項(xiàng)示例清單中的視圖控制器可以簡(jiǎn)潔高效,意義明確地控制視圖。你也會(huì)發(fā)現(xiàn)視圖控制器中代碼和所有的其他類(lèi)很容易理解,容易測(cè)試,理所當(dāng)然也更易維護(hù)。

基于用例的應(yīng)用設(shè)計(jì)

應(yīng)用通常是一些用戶(hù)用例的集合。用例也被稱(chēng)為驗(yàn)收標(biāo)準(zhǔn),或行為集,它們用來(lái)描述應(yīng)用的用途。清單可以根據(jù)時(shí)間,類(lèi)型以及名字排序,這就是一個(gè)用例。用例是應(yīng)用程序中用來(lái)負(fù)責(zé)業(yè)務(wù)邏輯的一層,應(yīng)獨(dú)立于用戶(hù)界面的實(shí)現(xiàn),同時(shí)要足夠小,并且有良好的定義。決定如何將一個(gè)復(fù)雜的應(yīng)用分解成較小的用例非常具有挑戰(zhàn)性,并且需要長(zhǎng)期實(shí)踐,但這對(duì)于縮小你解決的問(wèn)題時(shí)所要面臨的范圍及完成的每個(gè)類(lèi)的所要涉及的內(nèi)容來(lái)說(shuō),是很有幫助的。

利用 VIPER 建立一個(gè)應(yīng)用需要實(shí)施一組套件來(lái)滿(mǎn)足所有的用例,應(yīng)用邏輯是實(shí)現(xiàn)用例的主要組成部分,但卻不是唯一。用例也會(huì)影響用戶(hù)界面。另一個(gè)重要的方面,是要考慮用例如何與其他應(yīng)用程序的核心組件相互配合,例如網(wǎng)絡(luò)和數(shù)據(jù)持久化。組件就好比用例的插件,VIPER 則用來(lái)描述這些組件的作用是什么,如何進(jìn)行交互。

我們其中一個(gè)用例,或者說(shuō)待辦事項(xiàng)清單中其中的一個(gè)需求是可以基于用戶(hù)的選擇來(lái)將待辦事項(xiàng)分組。通過(guò)分離的邏輯將數(shù)據(jù)組織成一個(gè)用例,我們能夠在測(cè)試時(shí)使用戶(hù)界面代碼保持干凈,用例更易組裝,從而確保它如我們預(yù)期的方式工作。

VIPER 的主要部分

VIPER 的主要部分是:

  • 視圖:根據(jù)展示器的要求顯示界面,并將用戶(hù)輸入反饋給展示器。
  • 交互器:包含由用例指定的業(yè)務(wù)邏輯。
  • 展示器:包含為顯示(從交互器接受的內(nèi)容)做的準(zhǔn)備工作的相關(guān)視圖邏輯,并對(duì)用戶(hù)輸入進(jìn)行反饋(從交互器獲取新數(shù)據(jù))。
  • 實(shí)體:包含交互器要使用的基本模型對(duì)象。
  • 路由:包含用來(lái)描述屏幕顯示和顯示順序的導(dǎo)航邏輯。

這種分隔形式同樣遵循單一責(zé)任原則。交互器負(fù)責(zé)業(yè)務(wù)分析的部分,展示器代表交互設(shè)計(jì)師,而視圖相當(dāng)于視覺(jué)設(shè)計(jì)師。

以下則是不同組件的相關(guān)圖解,并展示了他們之間是如何關(guān)聯(lián)的:

http://wiki.jikexueyuan.com/project/objc/images/13-16.png" alt="" />

雖然在應(yīng)用中 VIPER 的組件可以以任意順序?qū)崿F(xiàn),我們?cè)谶@里選擇按照我們推薦的順序來(lái)進(jìn)行介紹。你會(huì)注意到這個(gè)順序與構(gòu)建整個(gè)應(yīng)用的進(jìn)程大致符合 -- 首先要討論的是產(chǎn)品需要做什么,以及用戶(hù)會(huì)如何與之交互。

交互器

交互器在應(yīng)用中代表著一個(gè)獨(dú)立的用例。它具有業(yè)務(wù)邏輯以操縱模型對(duì)象(實(shí)體)執(zhí)行特定的任務(wù)。交互器中的工作應(yīng)當(dāng)獨(dú)立與任何用戶(hù)界面,同樣的交互器可以同時(shí)運(yùn)用于 iOS 應(yīng)用或者 OS X 應(yīng)用中。

由于交互器是一個(gè) PONSO (Plain Old NSObject,普通的 NSObject),它主要包含了邏輯,因此很容易使用 TDD 進(jìn)行開(kāi)發(fā)。

示例應(yīng)用的主要用例是向用戶(hù)展示所有的待辦事項(xiàng)(比如任何截止于下周末的任務(wù))。此類(lèi)用例的業(yè)務(wù)邏輯主要是找出今天至下周末之間將要到期的待辦事項(xiàng),然后為它們分配一個(gè)相對(duì)的截止日期,比如今天,明天,本周以?xún)?nèi),或者下周。

以下是來(lái)自 VTDListInteractor 的對(duì)應(yīng)方法:

- (void)findUpcomingItems
{
    __weak typeof(self) welf = self;
    NSDate* today = [self.clock today];
    NSDate* endOfNextWeek = [[NSCalendar currentCalendar] dateForEndOfFollowingWeekWithDate:today];
    [self.dataManager todoItemsBetweenStartDate:today endDate:endOfNextWeek completionBlock:^(NSArray* todoItems) {
        [welf.output foundUpcomingItems:[welf upcomingItemsFromToDoItems:todoItems]];
    }];
}

實(shí)體

實(shí)體是被交互器操作的模型對(duì)象,并且它們只被交互器所操作。交互器永遠(yuǎn)不會(huì)傳輸實(shí)體至表現(xiàn)層 (比如說(shuō)展示器)。

實(shí)體也應(yīng)該是 PONSOs。如果你使用 Core Data,最好是將托管對(duì)象保持在你的數(shù)據(jù)層之后,交互器不應(yīng)與 NSManageObjects 協(xié)同工作。

這里是我們的待辦事項(xiàng)服務(wù)的實(shí)體:

@interface VTDTodoItem : NSObject

@property (nonatomic, strong)   NSDate*     dueDate;
@property (nonatomic, copy)     NSString*   name;

+ (instancetype)todoItemWithDueDate:(NSDate*)dueDate name:(NSString*)name;

@end

不要詫異于你的實(shí)體僅僅是數(shù)據(jù)結(jié)構(gòu),任何依賴(lài)于應(yīng)用的邏輯都應(yīng)該放到交互器中。

展示器

展示器是一個(gè)主要包含了驅(qū)動(dòng)用戶(hù)界面的邏輯的 PONSO,它總是知道何時(shí)呈現(xiàn)用戶(hù)界面?;谄涫占瘉?lái)自用戶(hù)交互的輸入功能,它可以在合適的時(shí)候更新用戶(hù)界面并向交互器發(fā)送請(qǐng)求。

當(dāng)用戶(hù)點(diǎn)擊 “+” 鍵新建待辦事項(xiàng)時(shí),addNewEntry 被調(diào)用。對(duì)于此項(xiàng)操作,展示器會(huì)要求 wireframe 顯示用戶(hù)界面以增加新項(xiàng)目:

- (void)addNewEntry
{
    [self.listWireframe presentAddInterface];
}

展示器還會(huì)從交互器接收結(jié)果并將結(jié)果轉(zhuǎn)換成能夠在視圖中有效顯示的形式。

下面是如何從交互器接受待辦事項(xiàng)的過(guò)程,其中包含了處理數(shù)據(jù)的過(guò)程并決定展現(xiàn)給用戶(hù)哪些內(nèi)容:

- (void)foundUpcomingItems:(NSArray*)upcomingItems
{
    if ([upcomingItems count] == 0)
    {
        [self.userInterface showNoContentMessage];
    }
    else
    {
        [self updateUserInterfaceWithUpcomingItems:upcomingItems];
    }
}

實(shí)體永遠(yuǎn)不會(huì)由交互器傳輸給展示器,取而代之,那些無(wú)行為的簡(jiǎn)單數(shù)據(jù)結(jié)構(gòu)會(huì)從交互器傳輸?shù)秸故酒髂抢铩_@就防止了那些“真正的工作”在展示器那里進(jìn)行,展示器只能負(fù)責(zé)準(zhǔn)備那些在視圖里顯示的數(shù)據(jù)。

視圖

視圖一般是被動(dòng)的,它通常等待展示器下發(fā)需要顯示的內(nèi)容,而不會(huì)向其索取數(shù)據(jù)。視圖(例如登錄界面的登錄視圖控件)所定義的方法應(yīng)該允許展示器在高度抽象的層次與之交流。展示器通過(guò)內(nèi)容進(jìn)行表達(dá),而不關(guān)心那些內(nèi)容所顯示的樣子。展示器不知道 UILabel,UIButton 等的存在,它只知道其中包含的內(nèi)容以及何時(shí)需要顯示。內(nèi)容如何被顯示是由視圖來(lái)進(jìn)行控制的。

視圖是一個(gè)抽象的接口 (Interface),在 Objective-C 中使用協(xié)議被定義。一個(gè) UIViewController 或者它的一個(gè)子類(lèi)會(huì)實(shí)現(xiàn)視圖協(xié)議。比如我們的示例中 “添加” 界面會(huì)有以下接口:

@protocol VTDAddViewInterface <NSObject>

- (void)setEntryName:(NSString *)name;
- (void)setEntryDueDate:(NSDate *)date;

@end

視圖和視圖控制器同樣會(huì)操縱用戶(hù)界面和相關(guān)輸入。因?yàn)橥ǔ?lái)說(shuō)視圖控制器是最容易處理這些輸入和執(zhí)行某些操作的地方,所以也就不難理解為什么視圖控制器總是這么大了。為了使視圖控制器保持苗條,我們需要使它們?cè)谟脩?hù)進(jìn)行相關(guān)操作的時(shí)候可以有途徑來(lái)通知相關(guān)部分。視圖控制器不應(yīng)當(dāng)根據(jù)這些行為進(jìn)行相關(guān)決定,但是它應(yīng)當(dāng)將發(fā)生的事件傳遞到能夠做決定的部分。

在我們的例子中,Add View Controller 有一個(gè)事件處理的屬性,它實(shí)現(xiàn)了如下接口:

@protocol VTDAddModuleInterface <NSObject>

- (void)cancelAddAction;
- (void)saveAddActionWithName:(NSString *)name dueDate:(NSDate *)dueDate

@end

當(dāng)用戶(hù)點(diǎn)擊取消鍵的時(shí)候,視圖控制器告知這個(gè)事件處理程序用戶(hù)需要其取消這次添加的動(dòng)作。這樣一來(lái),事件處理程序便可以處理關(guān)閉 add view controller 并告知列表視圖進(jìn)行更新。

視圖和展示器之間邊界處是一個(gè)使用 ReactiveCocoa 的好地方。在這個(gè)示例中,視圖控制器可以返回一個(gè)代表按鈕操作的信號(hào)。這將允許展示器在不打破職責(zé)分離的前提下輕松地對(duì)那些信號(hào)進(jìn)行響應(yīng)。

路由

屏幕間的路徑會(huì)在交互設(shè)計(jì)師創(chuàng)建的線框 (wireframes) 里進(jìn)行定義。在 VIPER 中,路由是由兩個(gè)部分來(lái)負(fù)責(zé)的:展示器和線框。一個(gè)線框?qū)ο蟀?UIWindowUINavigationController,UIViewController 等部分,它負(fù)責(zé)創(chuàng)建視圖/視圖控制器并將其裝配到窗口中。

由于展示器包含了響應(yīng)用戶(hù)輸入的邏輯,因此它就擁有知曉何時(shí)導(dǎo)航至另一個(gè)屏幕以及具體是哪一個(gè)屏幕的能力。而同時(shí),線框知道如何進(jìn)行導(dǎo)航。在兩者結(jié)合起來(lái)的情況下,展示器可以使用線框來(lái)進(jìn)行實(shí)現(xiàn)導(dǎo)航功能,它們兩者一起描述了從一個(gè)屏幕至另一個(gè)屏幕的路由過(guò)程。

線框同時(shí)也明顯是一個(gè)處理導(dǎo)航轉(zhuǎn)場(chǎng)動(dòng)畫(huà)的地方。來(lái)看看這個(gè) add wireframe 中的例子吧:

@implementation VTDAddWireframe

- (void)presentAddInterfaceFromViewController:(UIViewController *)viewController 
{
    VTDAddViewController *addViewController = [self addViewController];
    addViewController.eventHandler = self.addPresenter;
    addViewController.modalPresentationStyle = UIModalPresentationCustom;
    addViewController.transitioningDelegate = self;

    [viewController presentViewController:addViewController animated:YES completion:nil];

    self.presentedViewController = viewController;
}

#pragma mark - UIViewControllerTransitioningDelegate Methods

- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed 
{
    return [[VTDAddDismissalTransition alloc] init];
}

- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented
                                                                  presentingController:(UIViewController *)presenting
                                                                      sourceController:(UIViewController *)source 
{
    return [[VTDAddPresentationTransition alloc] init];
}

@end

應(yīng)用使用了自定義的視圖控制器轉(zhuǎn)場(chǎng)來(lái)呈現(xiàn) add view controller。因?yàn)榫€框部件負(fù)責(zé)實(shí)施這個(gè)轉(zhuǎn)場(chǎng),所以它成為了 add view controller 轉(zhuǎn)場(chǎng)的委托,并且返回適當(dāng)?shù)霓D(zhuǎn)場(chǎng)動(dòng)畫(huà)。

利用 VIPER 組織應(yīng)用組件

iOS 應(yīng)用的構(gòu)架需要考慮到 UIKit 和 Cocoa Touch 是建立應(yīng)用的主要工具。架構(gòu)需要和應(yīng)用的所有組件都能夠和平相處,但又需要為如何使用框架的某些部分以及它們應(yīng)該在什么位置提供一些指導(dǎo)和建議。

iOS 應(yīng)用程序的主力是 UIViewController,我們不難想象找一個(gè)競(jìng)爭(zhēng)者來(lái)取代 MVC 就可以避免大量使用視圖控制器。但是視圖控制器現(xiàn)在是這個(gè)平臺(tái)的核心:它們處理設(shè)備方向的變化,回應(yīng)用戶(hù)的輸入,和類(lèi)似導(dǎo)航控制器之類(lèi)的系統(tǒng)系統(tǒng)組件集成得很好,而現(xiàn)在在 iOS 7 中又能實(shí)現(xiàn)自定義屏幕之間的轉(zhuǎn)換,功能實(shí)在是太強(qiáng)大了。

有了 VIPER,視圖控制器便就能真正的做它本來(lái)應(yīng)該做的事情了,那就是控制視圖。 我們的待辦事項(xiàng)應(yīng)擁有兩個(gè)視圖控制器,一個(gè)是列表視圖,另一個(gè)是新建待辦。因?yàn)?add view controller 要做的所有事情就是控制視圖,所以實(shí)現(xiàn)起來(lái)非常的簡(jiǎn)單基礎(chǔ):

@implementation VTDAddViewController

- (void)viewDidAppear:(BOOL)animated 
{
    [super viewDidAppear:animated];

    UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
                                                                                        action:@selector(dismiss)];
    [self.transitioningBackgroundView addGestureRecognizer:gestureRecognizer];
    self.transitioningBackgroundView.userInteractionEnabled = YES;
}

- (void)dismiss 
{
    [self.eventHandler cancelAddAction];
}

- (void)setEntryName:(NSString *)name 
{
    self.nameTextField.text = name;
}

- (void)setEntryDueDate:(NSDate *)date 
{
    [self.datePicker setDate:date];
}

- (IBAction)save:(id)sender 
{
    [self.eventHandler saveAddActionWithName:self.nameTextField.text
                                     dueDate:self.datePicker.date];
}

- (IBAction)cancel:(id)sender 
{
    [self.eventHandler cancelAddAction];
}

#pragma mark - UITextFieldDelegate Methods

- (BOOL)textFieldShouldReturn:(UITextField *)textField 
{
    [textField resignFirstResponder];

    return YES;
}

@end

應(yīng)用在接入網(wǎng)絡(luò)以后會(huì)變得更有用處,但是究竟該在什么時(shí)候聯(lián)網(wǎng)呢?又由誰(shuí)來(lái)負(fù)責(zé)啟動(dòng)網(wǎng)絡(luò)連接呢?典型的情況下,由交互器來(lái)啟動(dòng)網(wǎng)絡(luò)連接操作的項(xiàng)目,但是它不會(huì)直接處理網(wǎng)絡(luò)代碼。它會(huì)尋找一個(gè)像是 network manager 或者 API client 這樣的依賴(lài)項(xiàng)。交互器可能聚合來(lái)自多個(gè)源的數(shù)據(jù)來(lái)提供所需的信息,從而完成一個(gè)用例。最終,就由展示器來(lái)采集交互器反饋的數(shù)據(jù),然后組織并進(jìn)行展示。

數(shù)據(jù)存儲(chǔ)模塊負(fù)責(zé)提供實(shí)體給交互器。因?yàn)榻换テ饕瓿蓸I(yè)務(wù)邏輯,因此它需要從數(shù)據(jù)存儲(chǔ)中獲取實(shí)體并操縱它們,然后將更新后的實(shí)體再放回?cái)?shù)據(jù)存儲(chǔ)中。數(shù)據(jù)存儲(chǔ)管理實(shí)體的持久化,而實(shí)體應(yīng)該對(duì)數(shù)據(jù)庫(kù)全然不知,正因如此,實(shí)體并不知道如何對(duì)自己進(jìn)行持久化。

交互器同樣不需要知道如何將實(shí)體持久化,有時(shí)交互器更希望使用一個(gè) data manager 來(lái)使其與數(shù)據(jù)存儲(chǔ)的交互變得容易。Data manager 可以處理更多的針對(duì)存儲(chǔ)的操作,比如創(chuàng)建獲取請(qǐng)求,構(gòu)建查詢(xún)等等。這就使交互器能夠?qū)⒏嗟淖⒁饬Ψ旁趹?yīng)用邏輯上,而不必再了解實(shí)體是如何被聚集或持久化的。下面我們舉一個(gè)例子來(lái)說(shuō)明使用 data manager 有意義的,這個(gè)例子假設(shè)你在使用 Core Data。這是示例應(yīng)用程序的 data manager 的接口:

@interface VTDListDataManager : NSObject

@property (nonatomic, strong) VTDCoreDataStore *dataStore;

- (void)todoItemsBetweenStartDate:(NSDate *)startDate endDate:(NSDate *)endDate completionBlock:(void (^)(NSArray *todoItems))completionBlock;

@end

當(dāng)使用 TDD 來(lái)開(kāi)發(fā)一個(gè)交互器時(shí),是可以用一個(gè)測(cè)試用的模擬存儲(chǔ)來(lái)代替生產(chǎn)環(huán)境的數(shù)據(jù)存儲(chǔ)的。避免與遠(yuǎn)程服務(wù)器通訊(網(wǎng)絡(luò)服務(wù))以及避免讀取磁盤(pán)(數(shù)據(jù)庫(kù))可以加快你測(cè)試的速度并加強(qiáng)其可重復(fù)性。

將數(shù)據(jù)存儲(chǔ)保持為一個(gè)界限清晰的特定層的原因之一是,這可以讓你延遲選擇一個(gè)特定的持久化技術(shù)。如果你的數(shù)據(jù)存儲(chǔ)是一個(gè)獨(dú)立的類(lèi),那你就可以使用一個(gè)基礎(chǔ)的持久化策略來(lái)開(kāi)始你的應(yīng)用,然后等到有意義的時(shí)候升級(jí)至 SQLite 或者 Core Data。而因?yàn)閿?shù)據(jù)存儲(chǔ)層的存在,你的應(yīng)用代碼庫(kù)中就不需要改變?nèi)魏螙|西。

在 iOS 的項(xiàng)目中使用 Core Data 經(jīng)常比構(gòu)架本身還容易引起更多爭(zhēng)議。然而,利用 VIPER 來(lái)使用 Core Data 將給你帶來(lái)使用 Core Data 的前所未有的良好體驗(yàn)。在持久化數(shù)據(jù)的工具層面上,Core Data 可以保持快速存取和低內(nèi)存占用方面,簡(jiǎn)直是個(gè)神器。但是有個(gè)很惱人的地方,它會(huì)像觸須一樣把 NSManagedObjectContext 延伸至你所有的應(yīng)用實(shí)現(xiàn)文件中,特別是那些它們不該待的地方。VIPER 可以使 Core Data 待在正確的地方:數(shù)據(jù)存儲(chǔ)層。

在待辦事項(xiàng)示例中,應(yīng)用僅有的兩部分知道使用了 Core Data,其一是數(shù)據(jù)存儲(chǔ)本身,它負(fù)責(zé)建立 Core Data 堆棧;另一個(gè)是 data manager。Data manager 執(zhí)行了獲取請(qǐng)求,將數(shù)據(jù)存儲(chǔ)返回的 NSManagedObject 對(duì)象轉(zhuǎn)換為標(biāo)準(zhǔn)的 PONSO 模型對(duì)象,并傳輸回業(yè)務(wù)邏輯層。這樣一來(lái),應(yīng)用程序核心將不再依賴(lài)于 Core Data,附加得到的好處是,你也再也不用擔(dān)心過(guò)期數(shù)據(jù) (stale) 和沒(méi)有良好組織的多線程 NSManagedObjects 來(lái)糟蹋你的工作成果了。

在通過(guò)請(qǐng)求訪問(wèn) Core Data 存儲(chǔ)時(shí),data manager 中看起來(lái)是這樣的:

@implementation VTDListDataManager

- (void)todoItemsBetweenStartDate:(NSDate *)startDate endDate:(NSDate*)endDate completionBlock:(void (^)(NSArray *todoItems))completionBlock
{
    NSCalendar *calendar = [NSCalendar autoupdatingCurrentCalendar];

    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(date >= %@) AND (date <= %@)", [calendar dateForBeginningOfDay:startDate], [calendar dateForEndOfDay:endDate]];
    NSArray *sortDescriptors = @[];

    __weak typeof(self) welf = self;
    [self.dataStore
     fetchEntriesWithPredicate:predicate
     sortDescriptors:sortDescriptors
     completionBlock:^(NSArray* entries) {
         if (completionBlock)
         {
             completionBlock([welf todoItemsFromDataStoreEntries:entries]);
         }
     }];
}

- (NSArray*)todoItemsFromDataStoreEntries:(NSArray *)entries
{
    return [entries arrayFromObjectsCollectedWithBlock:^id(VTDManagedTodoItem *todo) {
        return [VTDTodoItem todoItemWithDueDate:todo.date name:todo.name];
    }];
}

@end

與 Core Data 一樣極富爭(zhēng)議的恐怕就是 UI 故事板了。故事板具有很多有用的功能,如果完全忽視它將會(huì)是一個(gè)錯(cuò)誤。然而,調(diào)用故事版所能提供的所有功能來(lái)完成 VIPER 的所有目標(biāo)仍然是很困難的。

我們所能做出的妥協(xié)就是選擇不使用 segues 。有時(shí)候使用 segues 是有效的,但是使用 segues 的危險(xiǎn)性在于它們很難原封不動(dòng)地保持屏幕之間的分離,以及 UI 和應(yīng)用邏輯之間的分離。一般來(lái)說(shuō),如果實(shí)現(xiàn) prepareForSegue 方法是必須的話,我們就盡量不去使用 segues。

除此之外,故事板是一個(gè)實(shí)現(xiàn)用戶(hù)界面布局有效方法,特別是在使用自動(dòng)布局的時(shí)候。我們選擇在實(shí)現(xiàn)待辦事項(xiàng)兩個(gè)界面的實(shí)例中使用故事板,并且使用這樣的代碼來(lái)執(zhí)行自己的導(dǎo)航操作。

static NSString *ListViewControllerIdentifier = @"VTDListViewController";

@implementation VTDListWireframe

- (void)presentListInterfaceFromWindow:(UIWindow *)window 
{
    VTDListViewController *listViewController = [self listViewControllerFromStoryboard];
    listViewController.eventHandler = self.listPresenter;
    self.listPresenter.userInterface = listViewController;
    self.listViewController = listViewController;

    [self.rootWireframe showRootViewController:listViewController
                                      inWindow:window];
}

- (VTDListViewController *)listViewControllerFromStoryboard 
{
    UIStoryboard *storyboard = [self mainStoryboard];
    VTDListViewController *viewController = [storyboard instantiateViewControllerWithIdentifier:ListViewControllerIdentifier];
    return viewController;
}

- (UIStoryboard *)mainStoryboard 
{
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main"
                                                         bundle:[NSBundle mainBundle]];
    return storyboard;
}

@end

使用 VIPER 構(gòu)建模塊

一般在使用 VIPER 的時(shí)候,你會(huì)發(fā)現(xiàn)一個(gè)屏幕或一組屏幕傾向于聚在一起作為一個(gè)模塊。模塊可以以多種形式體現(xiàn),但一般最好把它想成是一種特性。在播客應(yīng)用中,一個(gè)模塊可能是音頻播放器或訂閱瀏覽器。然而在我們的待辦事項(xiàng)應(yīng)用中,列表和添加事項(xiàng)的屏幕都將作為單獨(dú)的模塊被建立。

將你的應(yīng)用作為一組模塊來(lái)設(shè)計(jì)有很多好處,其中之一就是模塊可以有非常明確和定義良好的接口,并且獨(dú)立于其他的模塊。這就使增加或者移除特性變得更加簡(jiǎn)單,也使在界面中向用戶(hù)展示各種可變模塊變得更加簡(jiǎn)單。

我們希望能將待辦事項(xiàng)中各模塊之間分隔更加明確,我們?yōu)樘砑幽K定義了兩個(gè)協(xié)議。一個(gè)是模塊接口,它定義了模塊可以做什么;另一個(gè)則是模塊的代理,用來(lái)描述該模塊做了什么。例如:

@protocol VTDAddModuleInterface <NSObject>

- (void)cancelAddAction;
- (void)saveAddActionWithName:(NSString *)name dueDate:(NSDate *)dueDate;

@end

@protocol VTDAddModuleDelegate <NSObject>

- (void)addModuleDidCancelAddAction;
- (void)addModuleDidSaveAddAction;

@end

因?yàn)槟K必須要被展示,才能對(duì)用戶(hù)產(chǎn)生價(jià)值,所以模塊的展示器通常需要實(shí)現(xiàn)模型的接口。當(dāng)另一個(gè)模型想要展現(xiàn)當(dāng)前模塊時(shí),它的展示器就需要實(shí)現(xiàn)模型的委托協(xié)議,這樣它就能在展示時(shí)知道當(dāng)前模塊做了些什么。

一個(gè)模塊可能包括實(shí)體,交互器和管理器的通用應(yīng)用邏輯層,這些通??捎糜诙鄠€(gè)屏幕。當(dāng)然,這取決于這些屏幕之間的交互及它們的相似度。一個(gè)模塊可以像在待辦事項(xiàng)列表里面一樣,簡(jiǎn)單的只代表一個(gè)屏幕。這樣一來(lái),應(yīng)用邏輯層對(duì)于它的特定模塊的行為來(lái)說(shuō)就非常特有了。

模塊同樣是組織代碼的簡(jiǎn)便途徑。將模塊所有的編碼都放在它自己的文件夾中并在 Xcode 中建一個(gè) group,這會(huì)在你需要尋找和改變更加容易。當(dāng)你在要尋找一個(gè)類(lèi)時(shí),它恰到好處地就在你所期待的地方,這種感覺(jué)真是無(wú)法形容的棒。

利用 VIPER 建立模塊的另一個(gè)好處是它使得擴(kuò)展到多平臺(tái)時(shí)變得更加簡(jiǎn)單。獨(dú)立在交互器層中的所有用例的應(yīng)用邏輯允許你可以專(zhuān)注于為平板,電話或者 Mac 構(gòu)建新的用戶(hù)界面,同時(shí)可以重用你的應(yīng)用層。

進(jìn)一步來(lái)說(shuō),iPad 應(yīng)用的用戶(hù)界面能夠?qū)⒉糠?iPhone 應(yīng)用的視圖,視圖控制器及展示器進(jìn)行再利用。在這種情況下,iPad 屏幕將由 ‘super’ 展示器和線框來(lái)代表,這樣可以利用 iPhone 使用過(guò)的展示器和線框來(lái)組成屏幕。建立進(jìn)而維護(hù)一個(gè)跨多平臺(tái)的應(yīng)用是一個(gè)巨大的挑戰(zhàn),但是好的構(gòu)架可以對(duì)整個(gè)模型和應(yīng)用層的再利用有大幅度的提升,并使其實(shí)現(xiàn)起來(lái)更加容易。

利用 VIPER 進(jìn)行測(cè)試

VIPER 的出現(xiàn)激發(fā)了一個(gè)關(guān)注點(diǎn)的分離,這使得采用 TDD 變得更加簡(jiǎn)便。交互器包含獨(dú)立與任何 UI 的純粹邏輯,這使測(cè)試驅(qū)動(dòng)開(kāi)發(fā)更加簡(jiǎn)單。同時(shí)展示器包含用來(lái)為顯示準(zhǔn)備數(shù)據(jù)的邏輯,并且它也獨(dú)立于任何一個(gè) UIKit 部件。對(duì)于這個(gè)邏輯的開(kāi)發(fā)也很容易用測(cè)試來(lái)驅(qū)動(dòng)。

我們更傾向于先從交互器下手。用戶(hù)界面里所有部分都服務(wù)于用例,而通過(guò)采用 TDD 來(lái)測(cè)試驅(qū)動(dòng)交互器的 API 可以讓你對(duì)用戶(hù)界面和用例之間的關(guān)系有一個(gè)更好的了解。

作為實(shí)例,我們來(lái)看一下負(fù)責(zé)待辦事項(xiàng)列表的交互器。尋找待辦事項(xiàng)的策略是要找出所有的將在下周末前截止的項(xiàng)目,并將這些項(xiàng)目分別歸類(lèi)至截止于今天,明天,本周或者下周。

我們編寫(xiě)的第一個(gè)測(cè)試是為了保證交互器能夠找到所有的截止于下周末的待辦事項(xiàng):

- (void)testFindingUpcomingItemsRequestsAllToDoItemsFromTodayThroughEndOfNextWeek
{
    [[self.dataManager expect] todoItemsBetweenStartDate:self.today endDate:self.endOfNextWeek completionBlock:OCMOCK_ANY];
    [self.interactor findUpcomingItems];
}

一旦知道了交互器找到了正確的待辦事項(xiàng)后,我們就需要編寫(xiě)幾個(gè)小測(cè)試用來(lái)確認(rèn)它確實(shí)將待辦事項(xiàng)分配到了正確的相對(duì)日期組內(nèi)(比如說(shuō)今天,明天,等等)。

- (void)testFindingUpcomingItemsWithOneItemDueTodayReturnsOneUpcomingItemsForToday
{
    NSArray *todoItems = @[[VTDTodoItem todoItemWithDueDate:self.today name:@"Item 1"]];
    [self dataStoreWillReturnToDoItems:todoItems];

    NSArray *upcomingItems = @[[VTDUpcomingItem upcomingItemWithDateRelation:VTDNearTermDateRelationToday dueDate:self.today title:@"Item 1"]];
    [self expectUpcomingItems:upcomingItems];

    [self.interactor findUpcomingItems];
}

既然我們已經(jīng)知道了交互器的 API 長(zhǎng)什么樣,接下來(lái)就是開(kāi)發(fā)展示器。一旦展示器接收到了交互器傳來(lái)的待辦事項(xiàng),我們就需要測(cè)試看看我們是否適當(dāng)?shù)膶?shù)據(jù)進(jìn)行格式化并且在用戶(hù)界面中正確的顯示它。

- (void)testFoundZeroUpcomingItemsDisplaysNoContentMessage
{
    [[self.ui expect] showNoContentMessage];

    [self.presenter foundUpcomingItems:@[]];
}

- (void)testFoundUpcomingItemForTodayDisplaysUpcomingDataWithNoDay
{
    VTDUpcomingDisplayData *displayData = [self displayDataWithSectionName:@"Today"
                                                          sectionImageName:@"check"
                                                                 itemTitle:@"Get a haircut"
                                                                itemDueDay:@""];
    [[self.ui expect] showUpcomingDisplayData:displayData];

    NSCalendar *calendar = [NSCalendar gregorianCalendar];
    NSDate *dueDate = [calendar dateWithYear:2014 month:5 day:29];
    VTDUpcomingItem *haircut = [VTDUpcomingItem upcomingItemWithDateRelation:VTDNearTermDateRelationToday dueDate:dueDate title:@"Get a haircut"];

    [self.presenter foundUpcomingItems:@[haircut]];
}

- (void)testFoundUpcomingItemForTomorrowDisplaysUpcomingDataWithDay
{
    VTDUpcomingDisplayData *displayData = [self displayDataWithSectionName:@"Tomorrow"
                                                          sectionImageName:@"alarm"
                                                                 itemTitle:@"Buy groceries"
                                                                itemDueDay:@"Thursday"];
    [[self.ui expect] showUpcomingDisplayData:displayData];

    NSCalendar *calendar = [NSCalendar gregorianCalendar];
    NSDate *dueDate = [calendar dateWithYear:2014 month:5 day:29];
    VTDUpcomingItem *groceries = [VTDUpcomingItem upcomingItemWithDateRelation:VTDNearTermDateRelationTomorrow dueDate:dueDate title:@"Buy groceries"];

    [self.presenter foundUpcomingItems:@[groceries]];
}

同樣需要測(cè)試的是應(yīng)用是否在用戶(hù)想要新建待辦事項(xiàng)時(shí)正確啟動(dòng)了相應(yīng)操作:

- (void)testAddNewToDoItemActionPresentsAddToDoUI
{
    [[self.wireframe expect] presentAddInterface];

    [self.presenter addNewEntry];
}

這時(shí)我們可以開(kāi)發(fā)視圖功能了,并且在沒(méi)有待辦事項(xiàng)的時(shí)候我們想要展示一個(gè)特殊的信息。

- (void)testShowingNoContentMessageShowsNoContentView
{
    [self.view showNoContentMessage];

    XCTAssertEqualObjects(self.view.view, self.view.noContentView, @"the no content view should be the view");
}

有待辦事項(xiàng)出現(xiàn)時(shí),我們要確保列表是顯示出來(lái)的:

- (void)testShowingUpcomingItemsShowsTableView
{
    [self.view showUpcomingDisplayData:nil];

    XCTAssertEqualObjects(self.view.view, self.view.tableView, @"the table view should be the view");
}

首先建立交互器是一種符合 TDD 的自然規(guī)律。如果你首先開(kāi)發(fā)交互器,緊接著是展示器,你就可以首先建立一個(gè)位于這些層的套件測(cè)試,并且為實(shí)現(xiàn)這是實(shí)例奠定基礎(chǔ)。由于你不需要為了測(cè)試它們而去與用戶(hù)界面進(jìn)行交互,所以這些類(lèi)可以進(jìn)行快速迭代。在你需要開(kāi)發(fā)視圖的時(shí)候,你會(huì)有一個(gè)可以工作并測(cè)試過(guò)的邏輯和表現(xiàn)層來(lái)與其進(jìn)行連接。在快要完成對(duì)視圖的開(kāi)發(fā)時(shí),你會(huì)發(fā)現(xiàn)第一次運(yùn)行程序時(shí)所有部件都運(yùn)行良好,因?yàn)槟闼幸淹ㄟ^(guò)的測(cè)試已經(jīng)告訴你它可以工作。

結(jié)論

我們希望你喜歡這篇對(duì) VIPER 的介紹?;蛟S你們都很好奇接下來(lái)應(yīng)該做什么,如果你希望通過(guò) VIPER 來(lái)對(duì)你下一個(gè)應(yīng)用進(jìn)行設(shè)計(jì),該從哪里開(kāi)始呢?

我們竭盡全力使這篇文章和我們利用 VIPER 實(shí)現(xiàn)的應(yīng)用實(shí)例足夠明確并且進(jìn)行了很好的定義。我們的待辦事項(xiàng)里列表程序相當(dāng)直接簡(jiǎn)單,但是它準(zhǔn)確地解釋了如何利用 VIPER 來(lái)建立一個(gè)應(yīng)用。在實(shí)際的項(xiàng)目中,你可以根據(jù)你自己的挑戰(zhàn)和約束條件來(lái)決定要如何實(shí)踐這個(gè)例子。根據(jù)以往的經(jīng)驗(yàn),我們的每個(gè)項(xiàng)目在使用 VIPER 時(shí)都或多或少地改變了一些策略,但它們無(wú)一例外的都從中得益,找到了正確的方向。

很多情況下由于某些原因,你可能會(huì)想要偏離 VIPER 所指引的道路??赡苣阌龅搅撕芏? 對(duì)象,或者你的應(yīng)用使用了故事板的 segues。沒(méi)關(guān)系的,在這些情況下,你只需要在做決定時(shí)稍微考慮下 VIPER 所代表的精神就好。VIPER 的核心在于它是建立在單一責(zé)任原則上的架構(gòu)。如果你碰到了些許麻煩,想想這些原則再考慮如何前進(jìn)。

你一定想知道在現(xiàn)有的應(yīng)用中能否只用 VIPER 。在這種情況下,你可以考慮使用 VIPER 構(gòu)建新的特性。我們?cè)S多現(xiàn)有項(xiàng)目都使用了這個(gè)方法。你可以利用 VIPER 建立一個(gè)模塊,這能幫助你發(fā)現(xiàn)許多建立在單一責(zé)任原則基礎(chǔ)上造成難以運(yùn)用架構(gòu)的現(xiàn)有問(wèn)題。

軟件開(kāi)發(fā)最偉大的事情之一就是每個(gè)應(yīng)用程序都是不同的,而設(shè)計(jì)每個(gè)應(yīng)用的架構(gòu)的方式也是不同的。這就意味著每個(gè)應(yīng)用對(duì)于我們來(lái)說(shuō)都是一個(gè)學(xué)習(xí)和嘗試的機(jī)遇,如果你決定開(kāi)始使用 VIPER,你會(huì)受益匪淺。感謝你的閱讀。

Swift 補(bǔ)充

蘋(píng)果上周在 WWDC 介紹了一門(mén)稱(chēng)之為 Swift 的編程語(yǔ)言來(lái)作為 Cocoa 和 Cocoa Touch 開(kāi)發(fā)的未來(lái)?,F(xiàn)在發(fā)表關(guān)于 Swift 的完整意見(jiàn)還為時(shí)尚早,但眾所周知編程語(yǔ)言對(duì)我們?nèi)绾卧O(shè)計(jì)和構(gòu)建應(yīng)用有著重大影響。我們決定使用 Swift 重寫(xiě)我們的待辦事項(xiàng)清單,幫助我們學(xué)習(xí)它對(duì) VIPER 意味著什么。至今為止,收獲頗豐。Swift 中的一些特性對(duì)于構(gòu)建應(yīng)用的體驗(yàn)有著顯著的提升。

結(jié)構(gòu)體

在 VIPER 中我們使用小型,輕量級(jí)的 model 類(lèi)來(lái)在比如從展示器到視圖這樣不同的層間傳遞數(shù)據(jù)。這些 PONSOs 通常是只是簡(jiǎn)單地帶有少量數(shù)據(jù),并且通常這些類(lèi)不會(huì)被繼承。Swift 的結(jié)構(gòu)體非常適合這個(gè)情況。下面的結(jié)構(gòu)體的例子來(lái)自 VIPER Swift。這個(gè)結(jié)構(gòu)體需要被判斷是否相等,所以我們重載了 == 操作符來(lái)比較這個(gè)類(lèi)型的兩個(gè)實(shí)例。

struct UpcomingDisplayItem : Equatable, Printable {
    let title : String = ""
    let dueDate : String = ""

    var description : String { get {
        return "\(title) -- \(dueDate)"
    }}

    init(title: String, dueDate: String) {
        self.title = title
        self.dueDate = dueDate
    }
}

func == (leftSide: UpcomingDisplayItem, rightSide: UpcomingDisplayItem) -> Bool {
    var hasEqualSections = false
    hasEqualSections = rightSide.title == leftSide.title

    if hasEqualSections == false {
        return false
    }

    hasEqualSections = rightSide.dueDate == rightSide.dueDate

    return hasEqualSections
}

類(lèi)型安全

也許 Objective-C 和 Swift 的最大區(qū)別是它們?cè)趯?duì)于類(lèi)型處理上的不同。 Objective-C 是動(dòng)態(tài)類(lèi)型,而 Swift 故意在編譯時(shí)做了嚴(yán)格的類(lèi)型檢查。對(duì)于一個(gè)類(lèi)似 VIPER 的架構(gòu), 應(yīng)用由不同層構(gòu)成,類(lèi)型安全是提升程序員效率和設(shè)計(jì)架構(gòu)有非常大的好處。編譯器幫助你確保正確類(lèi)型的容器和對(duì)象在層的邊界傳遞。如上所示,這是一個(gè)使用結(jié)構(gòu)體的好地方。如果一個(gè)結(jié)構(gòu)體的被設(shè)計(jì)為存在于兩層之間,那么由于類(lèi)型安全,你可以保證它將永遠(yuǎn)無(wú)法脫離這些層之間。

擴(kuò)展閱讀