版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2019.08.21 星期三 |
前言
GameplayKit框架,構建和組織你的游戲邏輯痴晦。 整合常見的游戲行為,如隨機數(shù)生成琳彩,人工智能誊酌,尋路和代理行為。接下來幾篇我們就一起看一下這個框架露乏。感興趣的看下面幾篇文章碧浊。
1. GameplayKit框架詳細解析(一) —— 基本概覽(一)
開始
首先看下主要內(nèi)容
在本教程中,您將使用
GameplayKit
的GKStateMachine
將iOS應用程序轉(zhuǎn)換為使用狀態(tài)機進行導航邏輯瘟仿。
下面看下寫作環(huán)境
Swift 5, iOS 13, Xcode 11
管理狀態(tài)很難辉词,但是您可以使用許多技術來管理常規(guī)iOS應用中的狀態(tài)。 GameplayKit
框架隱藏了一種有用的技術:GKStateMachine
猾骡。
狀態(tài)機通常用于游戲編程瑞躺。但是,它們的實用性并不止于此兴想。程序員已經(jīng)解決了狀態(tài)機的問題幢哨。 GKStateMachine
存在一個游戲開發(fā)框架內(nèi),但不要讓這一點阻止你嫂便。沒有理由你不能在任何其他有狀態(tài)管理的iOS應用程序中使用它捞镰。
狀態(tài)機的一些用途可能是理解復雜的業(yè)務邏輯加袋,控制視圖控制器的狀態(tài)或管理導航惊楼。在本教程中,您將重構應用程序以使用狀態(tài)機來控制導航翰灾。
當您完成本教程時厂画,您將更深入地了解狀態(tài)機并學習如何:
- 使用狀態(tài)機簡化應用程序中的邏輯凸丸。
- 使用
GKStateMachine
配置狀態(tài)機。 - 通過定義有效轉(zhuǎn)換來控制狀態(tài)機流袱院。
首先屎慢,打開示例項目。Kanji List
是一個學習日語Kanji
字符的應用程序忽洛,這對你下次去東京旅行肯定是不可或缺的腻惠。
該應用程序顯示了Kanji
列表。單擊Kanji
將顯示該Kanji
的含義以及使用Kanji
的單詞列表欲虚。單擊任何一個單詞將帶您進入單詞中的Kanji
列表集灌,您可以在其中重復該過程到您的內(nèi)容。
在本教程中复哆,您將重構Kanji List
的導航邏輯以使用狀態(tài)機欣喧。 目前,Kanji List
使用協(xié)調(diào)器模式(coordinator pattern)
進行導航寂恬。 這很棒续誉;這意味著導航代碼已經(jīng)從視圖控制器中提取出來。 您只需要添加狀態(tài)機來組織coordinators
初肉。
如果您不熟悉協(xié)調(diào)器模式酷鸦,請不要擔心。 這是一個簡單的模式牙咏,用于處理視圖控制器之間的應用程序流臼隔。
Understanding State Machines
狀態(tài)機是用于表示一次只能處于一個狀態(tài)的系統(tǒng)的數(shù)學抽象。 聽起來很復雜吧妄壶? 它實際上非常簡單摔握,也是一種非常有用的方法來查看一些問題。 例如丁寄,想想iPhone中的電池氨淌。 您可以將電池看作狀態(tài)機:
這個狀態(tài)機有四種狀態(tài):
- 1)Charging:手機已插入并處于充電狀態(tài)泊愧。
- 2)Fully Charged:電池已滿,手機正在通過充電器供電盛正。
- 3)Discharging:手機已拔下删咱,依靠電池供電。
- 4)Flat:電池電量不足豪筝。
將手機插入充電器會使其進入Charging狀態(tài)痰滋。 從那里,它可以在拔出時轉(zhuǎn)換到Discharging狀態(tài)续崖。 如果您將電話插入電池直到電池達到100%敲街,它將進入Fully Charged狀態(tài)。 拔下電話時严望,如果讓電池完全放電多艇,它將進入Flat狀態(tài)。
狀態(tài)機表示哪些狀態(tài)轉(zhuǎn)換有效著蟹。 電池在充電時不會flat
(除非您需要新電池6章),因此狀態(tài)機中沒有從Charging到Flat的轉(zhuǎn)換萧豆。
Different Between States
首先奸披,您需要定義不同的狀態(tài),以將Kanji
列表中的導航表示為狀態(tài)機涮雷。 在Xcode中打開入門項目后阵面,構建并運行應用程序。
該應用程序以所有支持的Kanji
列表開始洪鸭。 這將是All狀態(tài)样刷。 接下來,點擊Kanji
以顯示詳細信息屏幕览爵。 這將是Detail狀態(tài)置鼻。 最后,點擊使用Kanji
的一個單詞轉(zhuǎn)到Kanji
中的單詞列表蜓竹,您將其稱為List狀態(tài)箕母。
很好!由于Kanji List
是一個簡單的應用程序俱济,這三個狀態(tài)足以代表應用程序的功能嘶是。隨著應用程序功能的增長,狀態(tài)機將成為控制事物的有用工具蛛碌。
1. Transitioning Between States
在應用程序中定義不同的狀態(tài)有助于您更好地理解應用程序的工作方式聂喇。但是,如果沒有定義狀態(tài)之間的有效轉(zhuǎn)換蔚携,狀態(tài)機就不完整了希太。
每個屏幕都包含導航欄中的All按鈕克饶,以轉(zhuǎn)換到All屏幕。這意味著從Detail信息屏幕或List屏幕到All屏幕的轉(zhuǎn)換有效誊辉。
點擊一個單詞可將應用程序帶到List屏幕彤路,該屏幕顯示單詞中的所有kanji
。因此芥映,您只能通過Detail屏幕點擊單詞轉(zhuǎn)換到List屏幕。
此外远豺,點擊kanji
導航到Detail
屏幕奈偏。這意味著從All
屏幕或List
屏幕到Detail
屏幕的轉(zhuǎn)換有效。
將所有這些結合起來呈現(xiàn)狀態(tài)機的這個圖:
Creating the State Machine
現(xiàn)在躯护,關于狀態(tài)已經(jīng)討論很多了惊来。 是時候?qū)懸恍┐a了! 首先棺滞,在State Machine
組下創(chuàng)建一個名為KanjiStateMachine.swift
的文件裁蚁。 用以下內(nèi)容替換import
語句:
import UIKit
import GameplayKit.GKStateMachine
class KanjiStateMachine: GKStateMachine {
let presenter: UINavigationController
let kanjiStorage: KanjiStorage
init(presenter: UINavigationController,
kanjiStorage: KanjiStorage,
states: [GKState]) {
// 1
self.presenter = presenter
// 2
self.kanjiStorage = kanjiStorage
// 3
super.init(states: states)
}
}
雖然不需要繼承GKStateMachine
,但是這個子類允許您存儲狀態(tài)稍后需要的一些重要數(shù)據(jù)继准。 初始化程序很簡單枉证,下面是正在發(fā)生的事情:
- 1) 應用程序的協(xié)調(diào)器模式使用
UINavigationController
作為視圖控制器的演示者(presenter)
。 狀態(tài)機將擁有演示者移必。 - 2)
KanjiStorage
類本質(zhì)上是應用程序的字典室谚。 它存儲所有kanji
和包含它們的單詞。KanjiStateMachine
管理要使用的每個狀態(tài)的KanjiStorage
對象崔泵。 - 3)
GKStateMachine
的初始化程序需要您正在使用的每個狀態(tài)的實例秒赤,因此將其傳遞給GKStateMachine.init(states:)
。
接下來憎瘸,打開ApplicationCoordinator.swift
入篮。 這是應用程序的根協(xié)調(diào)器,它創(chuàng)建根視圖控制器并將其添加到應用程序的UIWindow
幌甘。 在類的頂部為狀態(tài)機添加新屬性:
let stateMachine: KanjiStateMachine
在init(window :)
結束時潮售,添加以下內(nèi)容以創(chuàng)建狀態(tài)機:
stateMachine = KanjiStateMachine(
presenter: rootViewController,
kanjiStorage: kanjiStorage,
states: [])
因為您還沒有創(chuàng)建任何GKState
類,所以您只需暫時為狀態(tài)傳遞一個空數(shù)組含潘。
Creating Each State
現(xiàn)在您已經(jīng)創(chuàng)建了狀態(tài)機饲做,現(xiàn)在是時候添加一些狀態(tài)了。 現(xiàn)在遏弱,應用程序中的每個協(xié)調(diào)員都會根據(jù)需要創(chuàng)建其他協(xié)調(diào)器以導航到不同的屏幕盆均。 因此,因為您想要移動決定導航到哪個屏幕漱逸,所以您需要從協(xié)調(diào)器中刪除該邏輯泪姨。
從ApplicationCoordinator
開始游沿。 此類將保留狀態(tài)機,但所有其他協(xié)調(diào)器將由狀態(tài)機中的某個狀態(tài)創(chuàng)建肮砾。 因此诀黍,刪除ApplicationCoordinator
上的allKanjiListCoordinator
屬性。 稍后您將在AllState
類中重新創(chuàng)建它仗处。 刪除創(chuàng)建協(xié)調(diào)器的init(window :)
中的這一行:
allKanjiListCoordinator = KanjiListCoordinator(
presenter: rootViewController,
kanjiStorage: kanjiStorage,
list: kanjiStorage.allKanji(),
title: "Kanji List")
還有啟動協(xié)調(diào)器的start()
內(nèi)部的這個方法:
allKanjiListCoordinator.start()
構建并運行應用程序眯勾。 好像它失去了一些功能:
當你開始創(chuàng)建狀態(tài)時,你將獲得它婆誓。
1. All State
在State Machine
組下添加名為AllState.swift
的新文件吃环。 用以下內(nèi)容替換其import
語句:
import GameplayKit.GKState
class AllState: GKState {
// 1
lazy var allKanjiListCoordinator = makeAllKanjiCoordinator()
// 2
override func didEnter(from previousState: GKState?) {
allKanjiListCoordinator?.start()
}
private func makeAllKanjiCoordinator() -> KanjiListCoordinator? {
// 3
guard let kanjiStateMachine = stateMachine as? KanjiStateMachine else {
return nil
}
let kanjiStorage = kanjiStateMachine.kanjiStorage
// 4
return KanjiListCoordinator(
presenter: kanjiStateMachine.presenter,
kanjiStorage: kanjiStorage,
list: kanjiStorage.allKanji(),
title: "Kanji List")
}
}
下面進行細分:
- 1) 在這里,您重新創(chuàng)建從
ApplicationCoordinator.swift
中刪除的協(xié)調(diào)器洋幻。 - 2) 只要狀態(tài)機進入新狀態(tài)郁轻,
didEnter(from :)
就會觸發(fā)。 它是觸發(fā)allKanjiListCoordinator
導航到All
屏幕的理想場所文留。 - 3) 您可以使用
GKState
上的stateMachine
屬性來獲取其狀態(tài)機好唯。 在這里,您將其強制轉(zhuǎn)換為KanjiStateMachine
以訪問您之前添加的屬性燥翅。 - 4) 要構建
KanjiListCoordinator
骑篙,請為其提供顯示All
屏幕所需的所有數(shù)據(jù)。
接下來权旷,打開ApplicationCoordinator.swift
并找到在init(window :)
中創(chuàng)建狀態(tài)機的行替蛉。 創(chuàng)建一個AllState
實例并將其傳遞給states
數(shù)組,如下所示:
stateMachine = KanjiStateMachine(
presenter: rootViewController,
kanjiStorage: kanjiStorage,
states: [AllState()])
在start()
中拄氯,將以下行添加到方法的開頭:
stateMachine.enter(AllState.self)
這會導致狀態(tài)機進入AllState
并觸發(fā)allKanjiListCoordinator
導航到All
屏幕躲查。 構建并運行應用程序。 一切都在順利進行译柏!
2. Detail State
打開KanjiListCoordinator.swift
并在底部的擴展中找到kanjiListViewController(_:didSelectKanji :)
镣煮。 此方法創(chuàng)建并啟動KanjiDetailCoordinator
,使應用程序?qū)Ш降?code>Detail屏幕鄙麦。 刪除方法的內(nèi)容典唇,將其留空。
構建并運行應用程序胯府。 它應該仍然顯示All
屏幕介衔。 但是,因為您從kanjiListViewController(_:didSelectKanji :)
中刪除了導航邏輯骂因,所以KanjiListCoordinator
不會創(chuàng)建下一個協(xié)調(diào)器來移動到不同的屏幕炎咖。 點擊一個kanji
什么也沒做。
要解決此問題,您需要將剛剛刪除的代碼添加到新的狀態(tài)對象中乘盼。 在State Machine
組下添加名為DetailState.swift
的新文件升熊。 用以下內(nèi)容替換其import
語句:
import GameplayKit.GKState
class DetailState: GKState {
// 1
var kanji: Kanji?
var kanjiDetailCoordinator: KanjiDetailCoordinator?
override func didEnter(from previousState: GKState?) {
guard
let kanji = kanji,
let kanjiStateMachine = (stateMachine as? KanjiStateMachine)
else {
return
}
// 2
let kanjiDetailCoordinator = KanjiDetailCoordinator(
presenter: kanjiStateMachine.presenter,
kanji: kanji,
kanjiStorage: kanjiStateMachine.kanjiStorage)
self.kanjiDetailCoordinator = kanjiDetailCoordinator
kanjiDetailCoordinator.start()
}
}
下面進行細分
- 1)
KanjiDetailCoordinator
需要一個Kanji
來顯示Detail
屏幕。 你需要在這里設置它绸栅。 - 2) 創(chuàng)建并啟動
KanjiDetailCoordinator
级野,類似于之前在kanjiListViewController(_:didSelectKanji :)
中的操作。
3. Communicating to the State Machine
您需要一種與進入DetailState
所需的狀態(tài)機進行通信的方法粹胯。 因此蓖柔,您將使用NotificationCenter
提交通知,然后在ApplicationCoordinator
中監(jiān)聽它风纠。 回到KanjiListCoordinator.swift
渊抽,將此行添加到kanjiListViewController(_:didSelectKanji :)
:
NotificationCenter.default
.post(name: Notifications.KanjiDetail, object: selectedKanji)
Notifications.KanjiDetail
只是提前為您創(chuàng)建的NSNotification.Name
對象。 這會發(fā)布通知议忽,傳遞顯示Detail
屏幕所需的selectedKanji
。
再次打開ApplicationCoordinator.swift
十减。 轉(zhuǎn)到在init(window :)
中創(chuàng)建狀態(tài)機的行栈幸。 創(chuàng)建一個DetailState
實例并將其傳遞給states
數(shù)組,就像之前為AllState
所做的那樣:
stateMachine = KanjiStateMachine(
presenter: rootViewController,
kanjiStorage: kanjiStorage,
states: [AllState(), DetailState()])
下面帮辟,添加這一行
@objc func receivedKanjiDetailNotification(notification: NSNotification) {
// 1
guard
let kanji = notification.object as? Kanji,
// 2
let detailState = stateMachine.state(forClass: DetailState.self)
else {
return
}
// 3
detailState.kanji = kanji
// 4
stateMachine.enter(DetailState.self)
}
下面進行細分:
- 1) 獲取隨通知
notification
一起傳遞的Kanji
對象 - 2)
GKStateMachine.state(forClass :)
返回傳遞給狀態(tài)機初始值設定項的狀態(tài)實例速址。 在這里獲取該實例。 - 3) 存儲在創(chuàng)建其
KanjiDetailCoordinator
時要使用的DetailState
的kanji
由驹。 - 4) 最后芍锚,輸入
DetailState
,它將創(chuàng)建并啟動KanjiDetailCoordinator
蔓榄。
您仍然需要訂閱KanjiDetail
通知并炮,因此將其添加到subscribeToNotifications()
:
NotificationCenter.default.addObserver(
self, selector: #selector(receivedKanjiDetailNotification),
name: Notifications.KanjiDetail, object: nil)
構建并運行應用程序。 您應該可以點擊一個Kanji
并再次到達詳細信息Detail
屏幕甥郑。
4. List State
實現(xiàn)ListState
的過程與您之前看到的類似逃魄。 您將從協(xié)調(diào)器中刪除導航邏輯,將其移動到新的GKState
類并與stateMachine
通信它應該進入新狀態(tài)澜搅。
首先伍俘,打開KanjiDetailCoordinator.swift
。 當用戶點擊詳細信息屏幕上的單詞時勉躺,會觸發(fā)kanjiDetailViewController(_:didSelectWord :)
癌瘾。 然后,它創(chuàng)建并啟動一個KanjiListCoordinator
饵溅,以顯示該單詞中所有漢字的列表屏幕妨退。
刪除kanjiDetailViewController(_:didSelectWord :)
的內(nèi)容并將其替換為:
NotificationCenter.default.post(name: Notifications.KanjiList, object: word)
回到ApplicationCoordinator.swift
,創(chuàng)建一個新的空方法來接收通知:
@objc func receivedKanjiListNotification(notification: NSNotification) {
}
然后,添加以下代碼以訂閱subscribeToNotifications()
中的通知碧注。
NotificationCenter.default.addObserver(
self, selector: #selector(receivedKanjiListNotification),
name: Notifications.KanjiList, object: nil)
在State Machine
組下嚣伐,創(chuàng)建一個名為ListState.swift
的新文件。 用以下內(nèi)容替換其import
語句:
import GameplayKit.GKState
class ListState: GKState {
// 1
var word: String?
var kanjiListCoordinator: KanjiListCoordinator?
override func didEnter(from previousState: GKState?) {
guard
let word = word,
let kanjiStateMachine = (stateMachine as? KanjiStateMachine)
else {
return
}
let kanjiStorage = kanjiStateMachine.kanjiStorage
// 2
let kanjiForWord = kanjiStorage.kanjiForWord(word)
// 3
let kanjiListCoordinator = KanjiListCoordinator(
presenter: kanjiStateMachine.presenter, kanjiStorage: kanjiStorage,
list: kanjiForWord, title: word)
self.kanjiListCoordinator = kanjiListCoordinator
kanjiListCoordinator.start()
}
}
它與您用于DetailState
的模式相同萍丐,但這是正在發(fā)生的事情:
- 1) 列表屏幕顯示單詞中的所有
kanji
轩端。 所以,在這里存儲這個詞逝变,以便從中獲取kanji
基茵。 - 2) 使用
KanjiStorage
對象從單詞中獲取kanji
列表。 - 3) 將所有必要的數(shù)據(jù)傳遞到
KanjiListCoordinator
的初始化程序中壳影,并調(diào)用start()
導航到List
屏幕拱层。
現(xiàn)在您已經(jīng)擁有了ListState
,您可以將其傳遞到狀態(tài)機并在需要時進入狀態(tài)宴咧。 回到ApplicationCoordinator.swift
根灯,在init(window :)
中將ListState
的實例傳遞給KanjiStateMachine
的初始化器:
stateMachine = KanjiStateMachine(
presenter: rootViewController,
kanjiStorage: kanjiStorage,
states: [AllState(), DetailState(), ListState()])
將以下內(nèi)容添加到receivedKanjiListNotification(notification :)
以配置并輸入ListState
:
// 1
guard
let word = notification.object as? String,
let listState = stateMachine.state(forClass: ListState.self)
else {
return
}
// 2
listState.word = word
// 3
stateMachine.enter(ListState.self)
這是細分:
- 1) 從通知和狀態(tài)機中的
ListState
實例獲取單詞。 - 2) 在
ListState
上設置狀態(tài)以配置KanjiListCoordinator
掺栅。 - 3) 輸入
ListState
烙肺,使KanjiListCoordinator
開始導航到列表屏幕。
構建并運行應用程序氧卧。 一切都應該順利進行桃笙,全部由GKStateMachine
管理。
Using Other Abilities of GKStateMachine
還記得狀態(tài)機狀態(tài)之間的不同轉(zhuǎn)換嗎沙绝?
好吧搏明,您可以將這些轉(zhuǎn)換添加到GKState
類,以防止任何無效轉(zhuǎn)換發(fā)生闪檬。 打開AllState.swift
并添加以下方法:
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return false
}
isValidNextState(_ :)
允許您定義此GKState
可以達到的狀態(tài)星著。 因為它返回false
,狀態(tài)機將無法從此狀態(tài)轉(zhuǎn)換到任何其他狀態(tài)粗悯。 構建并運行應用程序强饮。 點擊kanji
什么也不做:
因為只有將AllState
移動到特定kanji
的詳細信息屏幕才有意義,唯一有效的下一個狀態(tài)是DetailState
为黎。 用以下內(nèi)容替換isValidNextState(_ :)
的內(nèi)容:
return stateClass == DetailState.self
構建并運行應用程序邮丰,您應該能夠再次訪問詳細信息屏幕。 接下來铭乾,將其添加到DetailState.swift
:
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return stateClass == AllState.self || stateClass == ListState.self
}
DetailState
可以移動到其余狀態(tài)中的任何一個剪廉,因此對于任一狀態(tài)都返回true
。
與DetailState
類似炕檩,將以下內(nèi)容添加到ListState.swift
:
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return stateClass == DetailState.self || stateClass == AllState.self
}
構建并運行應用程序斗蒋。 一切都應該仍然有用捌斧。
現(xiàn)在,對AllState
進行最后一次更改泉沾。 打開ApplicationCoordinator.swift
并查看receivedAllKanjiNotification()
捞蚂。
當點擊導航欄中的All
按鈕時,它會觸發(fā)通知跷究,ApplicationCoordinator
會彈出到根視圖控制器姓迅。 協(xié)調(diào)器不應該具有關于導航層次結構的這種知識。 它應該知道的是該應用程序是為了進入AllState
俊马。 因此丁存,刪除receivedAllKanjiNotification()
的內(nèi)容并將其替換為:
stateMachine.enter(AllState.self)
現(xiàn)在,不是直接彈出到根視圖控制器柴我,receiveAllKanjiNotification()
將只轉(zhuǎn)換到AllState
解寝。 構建并運行應用程序。 點擊All
按鈕時艘儒,它會將新的視圖控制器推入堆棧聋伦。 您仍然希望它pop
到根視圖控制器,而不是push
到新的視圖控制器界睁。 打開AllState.swift
并用以下內(nèi)容替換didEnter(from :)
的內(nèi)容:
if previousState == nil {
allKanjiListCoordinator?.start()
} else {
(stateMachine as? KanjiStateMachine)?.presenter
.popToRootViewController(animated: true)
}
當您調(diào)用GKStateMachine.enter(_ :)
時嘉抓,先前的狀態(tài)將傳遞到didEnter(from :)
到當前狀態(tài)。 如果這是狀態(tài)機的第一個狀態(tài)晕窑,則沒有先前的狀態(tài),因此previousState
將為nil
卵佛。 在這種情況下杨赤,您可以在allKanjiListCoordinator
上調(diào)用start()
。 但是如果存在先前的狀態(tài)截汪,則意味著您應該pop
到根視圖控制器以返回到All
屏幕疾牲。
構建并運行應用程序。 在List screen
或Detail screen
上衙解,All
按鈕應該會返回到All
屏幕阳柔。
都完成了,您重構了Kanji
列表以使用GKStateMachine
來管理應用中的導航蚓峦。 做得好舌剂!
Apple關于GKState和GKStateMachine的文檔非常寶貴。 您可能也有興趣了解有關state machines的更多信息暑椰。
后記
本篇主要講述了GameplayKit的實用狀態(tài)機霍转,感興趣的給個贊或者關注~~~