ご注意 この記事ではXcode 8とSwift 3を使用します。iOSアプリケーションのサイズが増加し続けると、
MVCパターンは「適切な」アーキテクチャソリューションとしての役割を徐々に失います。
iOS開発者向けには、MVVM、VIPER、
Ribletsなど、より効果的なアーキテクチャパターンを利用できます。 これらは非常に異なりますが、共通の目的があります。多方向のデータストリームで単一の責任の原則に基づいてコードをブロックに分割することです。 多方向ストリームでは、データは異なるモジュール間で異なる方向に移動します。
場合によっては、多方向データストリームを使用したくない(または必要ない)場合があります。代わりに、データを一方向に送信する必要があります。これは単方向データストリームです。 ReSwiftに関するこの記事では、打たれたトラックをオフにし、
MemoryTunesという
Memory Gameアプリケーションを作成するときに
ReSwiftフレームワークを使用して単方向データストリームを実装する方法を学習します。
しかし、最初に、ReSwiftとは何ですか?
ReSwiftの概要
ReSwiftは、Swiftで
Reduxアーキテクチャを実装するのに役立つ小さなフレームワークです。
ReSwiftには4つの主要なコンポーネントがあります。
- ビュー : ストアの変更に反応し、画面に表示します。 ビューはアクションを送信します。
- アクション :アプリケーションの状態変更を開始します。 アクションは Reducerによって処理されます。
- レデューサー : ストアに保存されているアプリケーションの状態を直接変更します。
- ストア :アプリケーションの現在の状態を保存します。 Viewsなどのその他のモジュールは、プラグインして変更に対応できます。
ReSwiftには多くの興味深い利点があります。
- 非常に強い制限 :実際にはそうすべきではない便利な場所に小さなコードを配置するのは魅力的です。 ReSwiftは、発生することと発生する場所に非常に強い制限を設定することにより、これを防ぎます。
- 単方向のデータフロー :多方向のデータフローを実装するアプリケーションは、読み取りとデバッグが非常に難しい場合があります。 1つの変更により、アプリケーション全体にデータを送信する一連のイベントが発生する可能性があります。 単方向フローはより予測可能であり、コードの読み取りに必要な認知負荷を大幅に削減します。
- テストの容易さ :ビジネスロジックのほとんどは、純粋な関数であるReducerに含まれています。
- プラットフォームに依存しない:すべてのReSwift要素(ストア、レデューサー、アクション)はプラットフォームに依存しません。 iOS、macOS、またはtvOSで簡単に再利用できます。
多方向または単方向フロー
データフローの意味を明確にするために、次の例を示します。 VIPERを使用して作成されたアプリケーションは、モジュール間の多方向データフローをサポートします。
VIPER-多方向データストリームReSwiftに基づいて構築されたアプリケーションの単方向データストリームと比較してください。
ReSwift-単方向データストリームデータは一方向にしか送信できないため、コードを視覚的に追跡し、アプリケーションの問題を特定する方がはるかに簡単です。
はじめに
現在、いくつかのソースコードとReSwiftを含むフレームワークのセットが含まれている
プロジェクトを
ダウンロードすることから始めます。
まず、ReSwiftで作業をセットアップする必要があります。 アプリケーションのコア:状態を作成することから始めます。
AppState.swiftを開き、StateTypeに対応するAppState構造体を作成します。
import ReSwift struct AppState: StateType { }
この構造は、アプリケーションの状態を決定します。
AppState値を含む
ストアを作成する前に、メインの
Reducerを作成する必要があります。
Reducerは、
Storeに保存されている
AppStateの現在の値を直接変更します。 アクションのみがReducerを実行して、アプリケーションの現在の状態を変更できます。 レデューサーは、受信するアクションに応じて現在の
AppState値を生成します。
ご注意 アプリケーションにはStoreが1つだけあり、メインのReducerは1つしかありません。AppReducer.swiftでメインレデューサーアプリケーション関数を作成します
import ReSwift func appReducer(action: Action, state: AppState?) -> AppState { return AppState() }
appReducerは、アクションを実行し、変更されたAppStateを返す関数です。 状態パラメーターは、アプリケーションの現在の状態です。 この関数は、受信したアクションに応じて、それに応じて状態を変更する必要があります。 これで、新しいAppState値のみが作成されます。ストアを構成するとすぐに戻ります。
今度は、アプリケーションの状態を保存するストアを作成します。レデューサーはそれを変更できます。
ストアは、アプリケーション全体の現在の状態を保存します。これは、AppState構造体の値です。
AppDelegate.swiftを開き、
インポートUIKitを次のものに置き換えます。
import ReSwift var store = Store<AppState>(reducer: appReducer, state: nil)
これにより、appReducerによって初期化されたグローバルストア変数が作成されます。 appReducerは、StoreブロックのメインのReducerであり、アクションを受信したときにストアをどのように変更するかに関する指示が含まれています。 これは反復的な変更ではなく元の作成であるため、空の状態を渡します。
アプリケーションをコンパイルして実行し、すべてが正しく行われたことを確認します。
これはあまりおもしろくありません...しかし、少なくともそれは動作します:]インターフェースナビゲーション
アプリケーションの最初の実際の状態を作成します。 インターフェースのナビゲート(ルーティング)から始めます。
アプリケーションへの移動(またはルーティング)は、ReSwiftだけでなく、すべてのアーキテクチャにとって困難な作業です。 MemoryTunesでは、enumで画面のリスト全体を定義し、AppStateに現在の値が含まれる単純なアプローチを使用する必要があります。 AppRouterはこの値の変更に応答し、画面に現在のステータスを表示します。
AppRouter.swiftを開き、
インポートUIKitを次のものに置き換えます。
import ReSwift enum RoutingDestination: String { case menu = "MenuTableViewController" case categories = "CategoriesTableViewController" case game = "GameViewController" }
この
列挙は、アプリケーションで表されるすべてのコントローラーを定義します。
これで、Stateアプリケーションに保存するものができました。 この場合、メイン状態構造(AppState)は1つだけですが、アプリケーションの状態をメイン状態で示されるサブ状態に分割できます。
これは良い習慣であるため、状態変数をサブ状態構造にグループ化します。
RoutingState.swiftを開き、ナビゲーション用に次のサブ状態構造を追加します。
import ReSwift struct RoutingState: StateType { var navigationState: RoutingDestination init(navigationState: RoutingDestination = .menu) { self.navigationState = navigationState } }
RoutingStateには、画面上の現在の宛先を表す
navigationStateが含まれます。
注: menuはnavigationStateのデフォルト値です。 RoutingStateの初期化時に別の値を指定しない限り、この値はアプリケーションの起動時にデフォルトで間接的に設定されます。AppState.swiftで 、構造に次を追加します。
let routingState: RoutingState
AppStateにはRoutingStateのサブ状態が含まれるようになりました。
アプリケーションを起動すると、問題が表示されます。
appReducer関数はコンパイルされなくなりました! これは、routingStateをAppStateに追加したが、デフォルトの初期化呼び出しには何も渡さなかったためです。 RoutingStateを作成するには、reducerが必要です。
レデューサーにはコア機能が1つしかありませんが、状態と同様に、レデューサーはサブレデューサーに分割する必要があります。
RoutingReducer.swiftに移動するには、次のレデューサーを追加します。
import ReSwift func routingReducer(action: Action, state: RoutingState?) -> RoutingState { let state = state ?? RoutingState() return state }
メインのReducerと同様に、routingReducerは受け取ったアクションに基づいて状態を変更し、それを返します。 アクションはまだないため、状態がnilでこの値が返されると、新しいRoutingStateが作成されます。
サブリデューサーは、対応するサブ状態の初期値を初期化する責任があります。
AppReducer.swiftに戻って、コンパイラの警告を修正します。 これに一致するようにappReducer関数を変更します。
return AppState(routingState: routingReducer(action: action, state: state?.routingState))
routingState引数をAppStateイニシャライザーに追加しました。 メインのreduserからのアクションと状態はroutingReducerに渡され、新しい状態が決定されます。 作成するすべてのサブステートとサブリデューサーに対してこれを繰り返す必要があるため、このルーチンに慣れてください。
購読する
デフォルトのメニューがRoutingStateに設定されていることを覚えていますか? これは実際にはアプリケーションの現在の状態です! あなたはただそれを購読したことはありません。
ビューだけでなく、どのクラスでもストアにサブスクライブできます。 クラスがストアにサブスクライブすると、現在の状態またはサブ状態で発生するすべての変更に関する情報を受け取ります。 routingStateを変更するときにUINavigationControllerの現在の画面を変更できるように、AppRouterでこれを行う必要があります。
AppRouter.swiftファイルを開き、
AppRouterを次のものに置き換えます。
final class AppRouter { let navigationController: UINavigationController init(window: UIWindow) { navigationController = UINavigationController() window.rootViewController = navigationController
上記のコードでは、AppRouterクラスを更新し、拡張機能を追加しました。 私たちがやったことを詳しく見てみましょう:
- AppStateがグローバルストアにサブスクライブされました。 クロージャー式で、selectは、routingStateの変更にサブスクライブしていることを示します。
- pushViewControllerは、インスタンス化してナビゲーションスタックに追加するために使用されます。 渡された識別子に基づいてコントローラーをロードするinstantiateViewControllerメソッドを使用します。
- routingStateが変更されるとすぐにnewStateがコールバックを受け取るように、 StoreSubscriberと一致するAppRouterを作成します。
- ルートView Controllerに電源を供給したくないので、現在の宛先がルートであるかどうかを確認してください。
- 状態が変化したら、state.navigationStateのrawValue(View Controllerの名前)を使用して、UINavigationControllerに新しい宛先を追加します。
AppRouterは、menuの初期値に応答して、MenuTableViewControllerを表示します。
アプリケーションをコンパイルして実行し、エラーが消えたことを確認します。
現在、MenuTableViewControllerが表示されていますが、空です。 ユーザーを他の画面にリダイレクトするメニューを表示します。
表示する
何でもStoreSubscriberにできますが、ほとんどの場合、状態の変更に応答するビューになります。 あなたの仕事は、MenuTableViewControlellerにサブメニュー(またはメニュー)の2つのオプションを表示させることです。 State / Reducerプロシージャの時間です!
MenuState.swiftに移動し、次のようにメニューの状態を作成します。
import ReSwift struct MenuState: StateType { var menuTitles: [String] init() { menuTitles = ["New Game", "Choose Category"] } }
MenuState構造はmenuTitlesの配列で構成され、テーブル形式で表示されるヘッダーで初期化します。
MenuReducer.swiftで、次のコードを使用してこの状態のレデューサーを作成します。
import ReSwift func menuReducer(action: Action, state: MenuState?) -> MenuState { return MenuState() }
MenuStateは静的であるため、状態変更の処理について心配する必要はありません。 したがって、新しいMenuStateが単に返されます。
AppState.swiftに戻ります。 AppStateの最後にMenuStateを追加します。
let menuState: MenuState
デフォルトのイニシャライザを再度変更したため、コンパイルされません。
AppReducer.swiftで 、
AppState初期化子を次のように変更します。
return AppState( routingState: routingReducer(action: action, state: state?.routingState), menuState: menuReducer(action: action, state: state?.menuState))
MenuStateを取得したら、それをサブスクライブし、それを使用してメニューをレンダリングします。
MenuTableViewController.swiftを開き、コードを次のコードに置き換えます。
import ReSwift final class MenuTableViewController: UITableViewController {
これで、コントローラーはMenuStateの変更をサブスクライブし、ユーザーインターフェイスに宣言的に状態を表示します。
- TableDataSourceはスタートアップシステムに含まれており、UITableViewの宣言型データソースとして機能します。
- viewWillAppearでmenuStateをサブスクライブします。 menuStateが変更されるたびに、newStateでコールバックを受け取るようになります。
- 必要に応じて、登録を解除します。
- これは宣言部です。 ここで、UITableViewを設定します。 コードで、状態がどのようにビューに変換されるかを明確に見ることができます。
ご注意 お気づきかもしれませんが、ReSwiftは不変性をサポートしています-オブジェクトではなく構造(値)を積極的に使用します。 また、宣言的なユーザーインターフェイスコードを作成することをお勧めします。 なんで?
StoreSubscriberで定義されたnewStateコールバックは、状態の変更を渡します。 パラメータの状態値を修正したくなるかもしれません。たとえば、
final class MenuTableViewController: UITableViewController { var currentMenuTitlesState: [String] ...
ただし、状態がどのようにビューに変換されるかを明確に示す宣言的なユーザーインターフェイスコードを記述する方が、より理解しやすく、はるかに使いやすくなります。 この例の問題は、UITableViewに宣言型APIがないことです。 これが、違いを解消するためにTableDataSourceを作成した理由です。 詳細に興味がある場合は、 TableDataSource.swiftをご覧ください 。アプリケーションをコンパイルして実行すると、メニューが表示されます。
アクション
これで既製のメニューができたので、それを使用して新しい画面を切り替えたり開いたりできたら素晴らしいと思います。 最初のアクションを作成します。
アクションはストアの変更を開始します。 アクションは、変数を含むことができる単純な構造です:アクションパラメーター。 Reducerは、生成されたアクションを処理し、アクションのタイプとそのパラメーターに応じてアプリケーションの状態を変更します。
RoutingAction.swiftでアクションを作成します。
import ReSwift struct RoutingAction: Action { let destination: RoutingDestination }
RoutingActionは、現在の宛先を変更します。
次に、メニュー項目を選択したときにRoutingActionを実行します。
MenuTableViewController.swiftを開き、次をMenuTableViewControllerに追加します。
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { var routeDestination: RoutingDestination = .categories switch(indexPath.row) { case 0: routeDestination = .game case 1: routeDestination = .categories default: break } store.dispatch(RoutingAction(destination: routeDestination)) }
これにより、選択した行に基づいてrouteDestination値が設定されます。 次に、ルーティングアクションをストアに渡すためにディスパッチが適用されます。
アクションの実行準備はできていますが、どの減速機でもサポートされていません。
RoutingReducer.swiftを開き、
routingReducerの内容を次のコードに置き換えます。これにより、状態が更新されます。
var state = state ?? RoutingState() switch action { case let routingAction as RoutingAction: state.navigationState = routingAction.destination default: break } return state
スイッチは、渡されたアクションがRoutingActionアクションであるかどうかをチェックします。 その場合、その宛先はRoutingStateを変更するために使用され、RoutingStateはその後戻ります。
アプリケーションをコンパイルして実行します。 これで、メニュー項目を選択すると、対応するView Controllerがメニューコントローラーの上に表示されます。
ステータス更新
現在のナビゲーション実装にエラーがあることに気づいたかもしれません。 [新しいゲーム]メニュー項目をクリックすると、RoutingStateのnavigationStateがメニューからゲームに変わります。 ただし、戻るボタンを押してメニューに戻ると、navigationStateは何も更新しません!
ReSwiftでは、ユーザーインターフェイスの現在の状態と同期した状態を維持することが重要です。 UIKitによって何かが完全に制御されている場合、たとえばUITextFieldでユーザーがテキストフィールドに戻ったり入力したりするナビゲーションについては、忘れがちです。
MenuTableViewControllerが表示されたときにnavigationStateを更新すると、これを修正できます。
MenuTableViewController.swiftで、viewWillAppearの下部に次の行を追加します。
store.dispatch(RoutingAction(destination: .menu))
ユーザーが[不名誉]ボタンをクリックした場合、このコードはストアを更新します。
アプリを起動して、ナビゲーションをもう一度確認してください。 III ...ナビゲーションが完全に不完全になりました。 何も表示されません。
AppRouter.swiftを開き
ます 。 pushViewControllerは、新しいnavigationStateが受信されるたびに呼び出されることに注意してください。 これは、もう一度RoutingDestinationメニューをクリックして更新していることを意味します!
MenuViewControllerが表示されない場合は、追加のチェックを実行する必要があります。 pushViewControllerの内容を次のものに置き換えます。
let viewController = instantiateViewController(identifier: identifier) let newViewControllerType = type(of: viewController) if let currentVc = navigationController.topViewController { let currentViewControllerType = type(of: currentVc) if currentViewControllerType == newViewControllerType { return } } navigationController.pushViewController(viewController, animated: animated)
最後のView Controllerの
type(of :)関数を呼び出し、クリックしたときに表示される新しいものと比較します。 一致する場合、2つの値を返します。
アプリケーションをコンパイルして実行すると、スタックを変更したときにメニュー設定が正しい場合、ナビゲーションが正常に機能するはずです。
通常、ユーザーインターフェイスを使用して状態を更新し、現在の状態を動的に確認することは複雑です。 これは、ReSwiftで作業するときに克服しなければならない課題の1つです。 幸いなことに、これは頻繁に発生する必要はありません。
カテゴリー
次に、一歩前進して、より複雑な画面であるCategoriesTableViewControllerを実装します。 ユーザーがお気に入りのアーティストを聴きながらMemoryを再生できるように、ユーザーが音楽カテゴリを選択できるようにする必要があります。
CategoriesState.swiftに状態を追加することから始めます。
import ReSwift enum Category: String { case pop = "Pop" case electronic = "Electronic" case rock = "Rock" case metal = "Metal" case rap = "Rap" } struct CategoriesState: StateType { let categories: [Category] var currentCategorySelected: Category init(currentCategory: Category) { categories = [ .pop, .electronic, .rock, .metal, .rap] currentCategorySelected = currentCategory } }
enumは、音楽のいくつかのカテゴリを定義します。 CategoriesStateには、利用可能なカテゴリの配列と、ステータス追跡用のcurrentCategorySelectedが含まれています。
ChangeCategoryAction.swiftに次を追加します。
import ReSwift struct ChangeCategoryAction: Action { let categoryIndex: Int }
これにより、categoryIndexを使用して音楽のカテゴリを参照するCategoryStateを変更できるアクションがトリガーされます。
次に、ChangeCategoryActionを受け入れ、更新された状態を保存するReducerを実装する必要があります。 CategoriesReducer.swiftを開き、次を追加します。
import ReSwift private struct CategoriesReducerConstants { static let userDefaultsCategoryKey = "currentCategoryKey" } private typealias C = CategoriesReducerConstants func categoriesReducer(action: Action, state: CategoriesState?) -> CategoriesState { var currentCategory: Category = .pop
他のレデューサーの場合と同様に、アクションによって状態を完全に更新するためのメソッドが形成されます。 この場合、選択したカテゴリも
UserDefaultsに保存します。 これが起こる方法の詳細:
- 現在のカテゴリは、利用可能であればUserDefaultsから読み込まれ、まだ作成されていない場合はCategoriesStateイメージの作成に使用されます。
- ChangeCategoryActionに応答して、状態を更新し、UserDefaultsに新しいカテゴリを保存します。
- getCurrentCategoryStateFromUserDefaultsは、UserDefaultsからカテゴリを読み込むヘルパー関数です。
- saveCurrentCategoryStateToUserDefaultsは、UserDefaultsにカテゴリを保存するヘルパー関数です。
ヘルパー関数も純粋なグローバル関数です。 それらをクラスまたは構造に入れることができますが、常にクリーンな状態に保つ必要があります。
当然、AppStateを新しい状態に更新する必要があります。
AppState.swiftを開き、次を構造の最後に追加します。
let categoriesState: CategoriesState
categoryStateは現在AppStateの一部です。 あなたはすでにそれをマスターしました!
AppReducer.swiftを開き、それに応じて戻り値を変更します。
return AppState( routingState: routingReducer(action: action, state: state?.routingState), menuState: menuReducer(action: action, state: state?.menuState), categoriesState: categoriesReducer(action:action, state: state?.categoriesState))
ここでは、categoryStateをappReducerに追加し、actionとcategoriesStateを渡します。
次に、MenuTableViewControllerに似たカテゴリ画面を作成する必要があります。 ストアに署名し、TableDataSourceを使用します。
CategoriesTableViewController.swiftを開き、内容を次のものに置き換えます。
import ReSwift final class CategoriesTableViewController: UITableViewController { var tableDataSource: TableDataSource<UITableViewCell, Category>? override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated)
これは、MenuTableViewControllerに非常に似ています。 ハイライトは次のとおりです。
- viewWillAppearで、categoryStateの変更をサブスクライブし、viewWillDisappearでサブスクライブを解除します。
- ユーザーがセルを選択すると、ChangeCategoryActionが呼び出されます。
- newStateで、現在選択されているカテゴリのチェックボックスをオンにします
すべてが設定されました。 これで、カテゴリを選択できます。 アプリケーションをコンパイルして実行し、[カテゴリの選択]を選択して、正しく動作することを確認します。
非同期タスク
非同期プログラミングは難しいタスクですか? はい! しかし、ReSwift用ではありません。
iTunes APIからメモリカードの画像を取得します。 ただし、最初にゲームの状態、レデューサー、およびそれに関連付けられたアクションを作成する必要があります。
GameState.swiftを開くと、ゲームカードを表すMemoryCard構造が表示されます。 これには、マップに表示されるimageUrlが含まれます。 isFlippedはカードの表面が見えるかどうかを示し、isAlreadyGuessedはカードが推測されたかどうかを示します。
このファイルにゲームの状態を追加します。 ファイルの先頭にあるReSwiftをインポートすることから始めます。
import ReSwift
次に、ファイルの最後に次のコードを追加します。 struct GameState: StateType { var memoryCards: [MemoryCard]
これにより、ゲームの状態が決まります。使用可能なメモリーカードの配列の内容に加えて、パラメーターを指定します。- ダウンロードインジケータ、表示または非表示。
- ゲームオーバーです。
GameReducer.swiftにGame Reducerを追加します。 import ReSwift func gameReducer(action: Action, state: GameState?) -> GameState { let state = state ?? GameState(memoryCards: [], showLoading: false, gameFinished: false) return state }
現時点では、新しいGameStateを作成するだけです。後でこれに戻ります。AppState.swiftファイルで、AppStateの最後にgameStateを追加します。 let gameState: GameState
でAppReducer.swift最終更新初期化子: return AppState( routingState: routingReducer(action: action, state: state?.routingState), menuState: menuReducer(action: action, state: state?.menuState), categoriesState: categoriesReducer(action:action, state: state?.categoriesState), gameState: gameReducer(action: action, state: state?.gameState))
ご注意 Action / Reducer / Stateプロシージャを実行した後、どのように予測可能、理解可能で、簡単に機能するかに注意してください。この手順は、ReSwiftの単方向性と各モジュールに対して設定される厳しい制限のため、プログラマーにとって便利です。ご存じのように、Reduceのみがアプリケーションのストアを変更でき、アクションのみがこの変更を開始できます。問題を探す場所と新しいコードを追加する場所がすぐにわかります。 SetCardsAction.swiftに次のコードを追加して、マップを更新するアクションを定義します。 import ReSwift struct SetCardsAction: Action { let cardImageUrls: [String] }
アクションは、GameStateのマップの画像URLを設定します。これで、最初の非同期アクションを作成する準備ができました。でFetchTunesAction.swift以下を追加します。 import ReSwift func fetchTunes(state: AppState, store: Store<AppState>) -> FetchTunesAction { iTunesAPI.searchFor(category: state.categoriesState.currentCategorySelected.rawValue) { imageUrls in store.dispatch(SetCardsAction(cardImageUrls: imageUrls)) } return FetchTunesAction() } struct FetchTunesAction: Action { }
fetchTunesメソッドは、iTunesAPI(スタータープロジェクトに含まれています)を使用して画像を取得します。クロージャを使用して、結果とともにSetCardsActionを送信します。ReSwiftの非同期タスクは非常に単純です。完了後にアクションを送信するだけです。以上です。
fetchTunesメソッドは、選択の開始を示すために使用されるFetchTunesActionを返します。GameReducer.swiftを開き、2つの新しいアクションのサポートを追加します。gameReducerの内容を次のものに置き換えます。 var state = state ?? GameState(memoryCards: [], showLoading: false, gameFinished: false) switch(action) {
状態を定数に変更してからアクションスイッチを起動し、次のアクションを実行します。- FetchTunesActionはshowLoadingをtrueに設定します。
- SetCardsActionでは、カードがランダムに選択され、showLoadingがfalseに設定されます。generateNewCardsは、MemoryGameLogic.swiftファイル(スタータープロジェクトに含まれているファイル)にあります。
GameViewControllerでカードをレイアウトします。セルを作成することから始めます。CardCollectionViewCell.swiftファイルを開き、次のメソッドをCardCollectionViewCellの最後に追加します。 func configureCell(with cardState: MemoryCard) { let url = URL(string: cardState.imageUrl)
configureCellメソッドは、次のアクションを実行します。- 画像をキャッシュするために優れたKingfisherライブラリを使用します。
- 推測されるか、逆さまになったカードを表示します。
次に、コレクションビューを実行してマップを表示します。テーブルビューと同様に、使用しているランチャーに含まれているCollectionDataSourceという名前のUICollectionViewの宣言ラッパーがあります。GameViewController.swiftを開き、最初にUIKitを次のものに置き換えます。 import ReSwift
GameViewControllerで、showGameFinishedAlertメソッドのすぐ上に次のコードを追加します。 var collectionDataSource: CollectionDataSource<CardCollectionViewCell, MemoryCard>? override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) store.subscribe(self) { $0.select { $0.gameState } } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) store.unsubscribe(self) } override func viewDidLoad() {
StoreSubscriberを選択するまで、これによりいくつかのコンパイラ警告が発生することに注意してください。ビューは、viewWillAppearのgameStateにサブスクライブし、viewWillDisappearのサブスクライブを解除します。viewDidLoadでは、次のアクションが実行されます。- FetchTunesが実行され、iTunes APIからの画像の受信が開始されます。
- セルは、configureCellの適切なモデルを取得するCollectionDataSourceを使用して構成されます。
次に、StoreSubscriberを実行するための拡張機能を追加する必要があります。ファイルの最後に次を追加します。
これにより、状態の変更を処理するためにnewStateがアクティブになります。データソースが更新されるだけでなく、- ダウンロードインジケータのステータスは、ステータスに応じて更新されます。
- ゲームを再起動し、ゲームオーバー時に警告を表示します。
ゲームをコンパイルして実行し、[ 新しいゲーム]を選択すると、マップが表示されます。プレイする
ゲームのロジックにより、プレーヤーは2枚のカードを裏返すことができます。それらが同じである場合、それらは開いたままです;そうでない場合、彼らは再び穴を掘ります。プレーヤーの目標は、最小試行回数ですべてのカードを開くことです。これを行うには、フリップアクションが必要です。FlipCardAction.swiftを開き、次を追加します。 import ReSwift struct FlipCardAction: Action { let cardIndexToFlip: Int }
FlipCardActionは、cardIndexToFlipを使用して、カードが反転したときにGameStateを更新します。gameReducerを変更してFlipCardActionをサポートし、ゲームアルゴリズムの魔法を実行します。GameReducer.swiftを開き、デフォルトの前に次のブロックを追加します。 case let flipCardAction as FlipCardAction: state.memoryCards = flipCard(index: flipCardAction.cardIndexToFlip, memoryCards: state.memoryCards) state.gameFinished = hasFinishedGame(cards: state.memoryCards)
FlipCardAction、flipCardの場合、メモリカードの状態の変更は、cardIndexToFlipおよびその他のゲームロジックに基づいています。 hasFinishedGameが呼び出されて、ゲームが終了したかどうかを判断し、それに応じて状態を更新します。両方の関数はMemoryGameLogic.swiftにあります。パズルの最後の部分は、カードを選択するときにフリップアクションを送信することです。これにより、ゲームロジックがトリガーされ、適切な状態が変更されます。GameViewController.swiftファイルで、UICollectionViewDelegate拡張機能を見つけます。 collectionViewに次を追加します(_:didSelectItemAt :): store.dispatch(FlipCardAction(cardIndexToFlip: indexPath.row))
コレクションビューでマップが選択されると、FlipCardActionを使用して、関連付けられたindexPath.rowが送信されます。ゲームを起動します。今、あなたは遊ぶことができます。戻って楽しんでください!:]次は?
MemoryTunesの最終バージョンはこちらからダウンロードできます。ReSwiftについて学ぶべきことがまだたくさんあります。- ソフトウェア:現在、SwiftでCross CuttingConcernを処理する良い方法はありません。ReSwiftでは、無料で入手できます。ReSwiftで利用可能なミドルウェア機能を使用して、さまざまな問題を解決できます。これにより、ロギング、統計、キャッシュの処理が簡単になります。
- . MemoryTunes. , ReSwift-Router . - , ?:]
- : ReSwift, , . Reducers , . , .
- : , ReSwift , . , .
- Persistence . , . — . ReSwift .
- : Redux : . ReSwift , Katana ReduxKit .
あなたはこのテーマに関するあなたの知識を展開したい場合は、聞くReSwiftの話を -話円座ベンジャミン(ベンジャミンENCZ)、クリエーターReSwiftでReSwiftのプロジェクトの興味深い例のもたくさん。最後に、Christian Tietzeのブログでは、ReSwiftに関する新しいトピックを取り上げています。ご質問、コメント、アイデアがある場合は、以下のフォーラムのディスカッションに参加してください!