
開發(fā)移動應(yīng)用是一項非常復(fù)雜的工作,但作為開發(fā)者,我們就是來解決這個復(fù)雜的。狀態(tài)機(jī)(state machine)是一個很好的工具,它可以幫助我們簡化開發(fā)中的復(fù)雜問題。因此,在本篇基于Swift語言的Xcode教程中,我們將學(xué)習(xí)為iOS 9和OS X 11 El Capitan系統(tǒng)開發(fā)App時,如何使用狀態(tài)機(jī)。
視圖控制器可以很容易地定義為一個嵌入多個復(fù)雜功能的類。例如,假設(shè)一個須通過社交網(wǎng)絡(luò)展示用戶個人信息的視圖控制器。我們遇到的第一個情況就是:視圖控制器的視圖出現(xiàn)時數(shù)據(jù)尚未加載到App中。在這種情況下,我們需要處理兩個狀態(tài):數(shù)據(jù)可用和數(shù)據(jù)不可用。視圖和相應(yīng)的布局依賴于當(dāng)前狀態(tài)。處理這兩個狀態(tài)的最簡單方法是定義一個boolean變量。如,若數(shù)據(jù)可用,設(shè)置boolean變量為true;若數(shù)據(jù)不可用,在設(shè)置boolean變量為……嗯,稍等,這里不簡單。因?yàn)椤皵?shù)據(jù)不可用”究竟是什么意思?可能是,當(dāng)我們從遠(yuǎn)程服務(wù)器調(diào)用時,數(shù)據(jù)尚不可用;也可能是,我們未能從遠(yuǎn)程服務(wù)器獲取到數(shù)據(jù),導(dǎo)致數(shù)據(jù)不可用?;谶@個“不可用”,我們需要根據(jù)數(shù)據(jù)的真實(shí)狀態(tài)來定義UI面板。
因此,現(xiàn)在產(chǎn)生了三種狀態(tài),我們的應(yīng)用程序需要能夠從三種狀態(tài)中來回切換。例如,當(dāng)從服務(wù)器接收到數(shù)據(jù)時,UI面板能從顯示動態(tài)指示器切換到隱藏動態(tài)指示器。但是如果應(yīng)用程序未能接收到數(shù)據(jù),我們需要一直隱藏動態(tài)指示器,并通知用戶“沒有數(shù)據(jù)”。
此時,初始視圖控制器已經(jīng)形成?,F(xiàn)在我們不僅需要跟蹤當(dāng)前狀態(tài),還需要跟蹤狀態(tài)轉(zhuǎn)換。
在iOS 9和OS X 11 El Capitan中,有一個新的API可以無縫地創(chuàng)建一個狀態(tài)機(jī)。這個新API是GameplayKit的一部分,通常用于開發(fā)視頻游戲。接下來,我們就來學(xué)習(xí)如何使用這個視頻游戲API來管理應(yīng)用程序狀態(tài)。
創(chuàng)建狀態(tài)
在這里會用到兩個類:GKState和GKStateMachine。首先,需要為狀態(tài)機(jī)創(chuàng)建狀態(tài)。為此,要使用子類GKState。
回顧之前視圖控制器的例子,我們需要一個狀態(tài)。例如,定義一個接收服務(wù)器返回數(shù)據(jù)的狀態(tài),我們需要使用CGState子類:
import UIKit
import GameplayKit
class RetrievingDataState: GKState {
unownedvarprofileViewController : ViewController?
override func isValidNextState(stateClass: AnyClass) -> Bool {
return(stateClass == DataAvailableState.self) || (stateClass ==? DataNotAvailableState.self)
}
override func didEnterWithPreviousState(previousState: GKState?) {
// Perform here the actions that you want to do with your UI when it enters this state like for example, showing and starting an activity indicator
profileViewController?.activityIndicator.startAnimating()
}
override func willExitWithNextState(nextState: GKState) {
// Perform here the actions that you want to do with your UI when it exits this state like for example, stopping the activity indicator.
profileViewController?.activityIndicator.stopAnimating()
}
狀態(tài)機(jī)使用isValidNextState:方法來定義是否切換到了新狀態(tài)。返回true說明狀態(tài)可切換。在本文例子中,可以從RetrievingDataState切換到DataAvailableState或者DataNotAvailableState,這主要取決于服務(wù)器調(diào)用的結(jié)果。
didEnterWithPreviousState:和willExitWithNextState:兩個函數(shù)用來定義當(dāng)進(jìn)入或退出一個狀態(tài)時,需要進(jìn)行什么操作。本例中,當(dāng)進(jìn)入RetrievingDataState狀態(tài)時,啟動一個活動指示器來顯示此應(yīng)用程序正在處理一些功能;當(dāng)退出該狀態(tài)時,需要停止這個活動指示器。
后續(xù)如果我們接收到數(shù)據(jù)(如帶有信息的標(biāo)簽),需要再次展示某些功能時,我們可以再進(jìn)行這個操作。我們甚至可以基于即將接收或者退出的狀態(tài)來定義所要執(zhí)行的動作。因?yàn)槲覀儗⑺械倪壿嫶鎯υ谝粋€獨(dú)立并明確定義的地方,這將便于我們很好地控制每個情況下要如何操作,并在一定程度上可以使視圖控制器更加輕量。
注意:我已經(jīng)添加了一個屬性來保存指向視圖控制器的引用,所以我們可以通過狀態(tài)子類來執(zhí)行其方法以及訪問其屬性值。在這種情況下,這個引用必須是unowned,因?yàn)檫@個視圖控制器將擁有一個指向該狀態(tài)機(jī)的強(qiáng)引用,并且該狀態(tài)機(jī)也將用有一個指向每一個狀態(tài)的強(qiáng)引用。如果這個狀態(tài)擁有一個指向視圖控制器的強(qiáng)引用,它將引起一個循環(huán)保留。因此,為避免這個情況,這個引用必須是unowned的。
為了實(shí)現(xiàn)其他的狀態(tài),我們再創(chuàng)建一個GKState的子類:
41class?DataAvailableState:?GKState?{
unownedvarprofileViewController?:?ViewController?
override?func?isValidNextState(stateClass:?AnyClass)?->?Bool?{
returnstateClass?==?RetrievingDataState.self
}
override?func?didEnterWithPreviousState(previousState:?GKState?)?{
iflet?viewController?=?profileViewController?{
//?Perform?here?the?actions?that?you?want?to?do?with?your?UI?when?it?enters?this?state.
}
}
override?func?willExitWithNextState(nextState:?GKState)?{
iflet?viewController?=?profileViewController?{
//?Perform?here?the?actions?that?you?want?to?do?with?your?UI?when?it?exits?this?state.
}
}
}
class?DataNotAvailableState:?GKState?{
unownedvarprofileViewController?:?ViewController?
override?func?isValidNextState(stateClass:?AnyClass)?->?Bool?{
returnstateClass?==?RetrievingDataState.self
}
override?func?didEnterWithPreviousState(previousState:?GKState?)?{
iflet?viewController?=?profileViewController?{
//?Perform?here?the?actions?that?you?want?to?do?with?your?UI?when?it?enters?this?state.
}
}
override?func?willExitWithNextState(nextState:?GKState)?{
iflet?viewController?=?profileViewController?{
//?Perform?here?the?actions?that?you?want?to?do?with?your?UI?when?it?exits?this?state.
}
}
}
創(chuàng)建狀態(tài)機(jī)
一旦我們擁有一些狀態(tài),我們就能創(chuàng)建狀態(tài)機(jī)。
首先為上文提到的每個狀態(tài)分別創(chuàng)建一個實(shí)例,然后為每個實(shí)例分配一個指向視圖控制器的引用:
6let?retrievingDataState?=?RetrievingDataState()
retrievingDataState.profileViewController?=?self
let?dataAvailableState?=?DataAvailableState()
dataAvailableState.profileViewController?=?self
let?dataNotAvailableState?=?DataNotAvailableState()
dataNotAvailableState.profileViewController?=?self
最后,為狀態(tài)機(jī)創(chuàng)建數(shù)組來傳遞狀態(tài):
let?stateMachine?=?GKStateMachine(states:?[retrievingDataState,?dataAvailableState,?dataNotAvailableState])
啟動狀態(tài)機(jī)進(jìn)入其中一個狀態(tài)。本例中,第一個狀態(tài)是RetrievingDataState。
stateMachine.enterState(RetrievingDataState)
狀態(tài)機(jī)進(jìn)入這個狀態(tài),然后執(zhí)行didEnterWithPreviousState:方法。此時,動態(tài)指示器將出現(xiàn)在這個屏幕上。當(dāng)執(zhí)行這個方法時,previousState的值為nil,狀態(tài)機(jī)第一個進(jìn)入的狀態(tài)是becauseRetrievingDataState。
當(dāng)你接收到這個服務(wù)器的響應(yīng)時,根據(jù)結(jié)果,你將改變狀態(tài):
//?When?you?receive?updated?data?from?the?server
stateMachine.enterState(DataAvailableState)
退出RetrievingDataState時,程序?qū)?zhí)行willExitWithNextState:方法,同時動態(tài)指示器將停止。之后,將執(zhí)行DataAvailableState狀態(tài)的didEnterWithPreviousState:方法,以此類推。
總結(jié)
在應(yīng)用程序開發(fā)過程中,我們通常要處理多個狀態(tài)。同時處理這么多的狀態(tài)時,事情就會很復(fù)雜:網(wǎng)絡(luò)連接是否可用?服務(wù)器是否可用?數(shù)據(jù)是否已更新?數(shù)據(jù)是否無效?應(yīng)用程序是否添加背景?打開推送通知是否啟動應(yīng)用程序……等等。
嘗試同時處理所有這些狀態(tài)是一項非常復(fù)雜的工作。但是 GKState和CGStateMachine可以幫助我們非常清晰地定義每個狀態(tài),并且處理起來非常簡單。
享受編程吧!!
Vicente Vicens
本文首發(fā)于CocoaChina,譯者Purpleen,編譯自《GameplayKit: State Machine for non-game Apps》,轉(zhuǎn)載請注明出處。