ćę³Øę ćć®čØäŗć§ćÆ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 // 1 store.subscribe(self) { $0.select { $0.routingState } } } // 2 fileprivate func pushViewController(identifier: String, animated: Bool) { let viewController = instantiateViewController(identifier: identifier) navigationController.pushViewController(viewController, animated: animated) } private func instantiateViewController(identifier: String) -> UIViewController { let storyboard = UIStoryboard(name: "Main", bundle: nil) return storyboard.instantiateViewController(withIdentifier: identifier) } } // MARK: - StoreSubscriber // 3 extension AppRouter: StoreSubscriber { func newState(state: RoutingState) { // 4 let shouldAnimate = navigationController.topViewController != nil // 5 pushViewController(identifier: state.navigationState.rawValue, animated: shouldAnimate) } }
äøčØć®ć³ć¼ćć§ćÆć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 { // 1 var tableDataSource: TableDataSource<UITableViewCell, String>? override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) // 2 store.subscribe(self) { $0.select { $0.menuState } } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) // 3 store.unsubscribe(self) } } // MARK: - StoreSubscriber extension MenuTableViewController: StoreSubscriber { func newState(state: MenuState) { // 4 tableDataSource = TableDataSource(cellIdentifier:"TitleCell", models: state.menuTitles) {cell, model in cell.textLabel?.text = model cell.textLabel?.textAlignment = .center return cell } tableView.dataSource = tableDataSource tableView.reloadData() } }
ććć§ćć³ć³ććć¼ć©ć¼ćÆ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 // 1 if let loadedCategory = getCurrentCategoryStateFromUserDefaults() { currentCategory = loadedCategory } var state = state ?? CategoriesState(currentCategory: currentCategory) switch action { case let changeCategoryAction as ChangeCategoryAction: // 2 let newCategory = state.categories[changeCategoryAction.categoryIndex] state.currentCategorySelected = newCategory saveCurrentCategoryStateToUserDefaults(category: newCategory) default: break } return state } // 3 private func getCurrentCategoryStateFromUserDefaults() -> Category? { let userDefaults = UserDefaults.standard let rawValue = userDefaults.string(forKey: C.userDefaultsCategoryKey) if let rawValue = rawValue { return Category(rawValue: rawValue) } else { return nil } } // 4 private func saveCurrentCategoryStateToUserDefaults(category: Category) { let userDefaults = UserDefaults.standard userDefaults.set(category.rawValue, forKey: C.userDefaultsCategoryKey) userDefaults.synchronize() }
ä»ć®ć¬ćć„ć¼ćµć¼ć®å “åćØåę§ć«ćć¢ćÆć·ć§ć³ć«ćć£ć¦ē¶ę ćå®å Øć«ę“ę°ććććć®ć”ć½ćććå½¢ęććć¾ćć ćć®å “åćéøęććć«ćć“ćŖć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) // 1 store.subscribe(self) { $0.select { $0.categoriesState } } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) store.unsubscribe(self) } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // 2 store.dispatch(ChangeCategoryAction(categoryIndex: indexPath.row)) } } // MARK: - StoreSubscriber extension CategoriesTableViewController: StoreSubscriber { func newState(state: CategoriesState) { tableDataSource = TableDataSource(cellIdentifier:"CategoryCell", models: state.categories) {cell, model in cell.textLabel?.text = model.rawValue // 3 cell.accessoryType = (state.currentCategorySelected == model) ? .checkmark : .none return cell } self.tableView.dataSource = tableDataSource self.tableView.reloadData() } }
ćććÆćMenuTableViewControllerć«éåøøć«ä¼¼ć¦ćć¾ćć ćć¤ć©ć¤ććÆꬔć®ćØććć§ćć
- viewWillAppearć§ćcategoryStateć®å¤ę“ććµćć¹ćÆć©ć¤ćććviewWillDisappearć§ćµćć¹ćÆć©ć¤ććč§£é¤ćć¾ćć
- ć¦ć¼ć¶ć¼ćć»ć«ćéøęćććØćChangeCategoryActionćå¼ć³åŗććć¾ćć
- newStateć§ćē¾åØéøęććć¦ććć«ćć“ćŖć®ćć§ććÆćććÆć¹ććŖć³ć«ćć¾ć
ćć¹ć¦ćčØå®ććć¾ććć ććć§ćć«ćć“ćŖćéøęć§ćć¾ćć ć¢ććŖć±ć¼ć·ć§ć³ćć³ć³ćć¤ć«ćć¦å®č”ćć[ć«ćć“ćŖć®éøę]ćéøęćć¦ćę£ććåä½ććććØćē¢ŗčŖćć¾ćć
éåęćæć¹ćÆ
éåęććć°ć©ćć³ć°ćÆé£ćććæć¹ćÆć§ććļ¼ ćÆćļ¼ ććććReSwiftēØć§ćÆććć¾ććć
iTunes APIććć”ć¢ćŖć«ć¼ćć®ē»åćåå¾ćć¾ćć ćć ććęåć«ć²ć¼ć ć®ē¶ę ćć¬ćć„ć¼ćµć¼ćććć³ććć«é¢é£ä»ććććć¢ćÆć·ć§ć³ćä½ęććåæ č¦ćććć¾ćć
GameState.swiftćéććØćć²ć¼ć ć«ć¼ććč”ØćMemoryCardę§é ćč”Øē¤ŗććć¾ćć ććć«ćÆććććć«č”Øē¤ŗćććimageUrlćå«ć¾ćć¾ćć isFlippedćÆć«ć¼ćć®č”Øé¢ćč¦ćććć©ćććē¤ŗććisAlreadyGuessedćÆć«ć¼ććęØęø¬ććććć©ćććē¤ŗćć¾ćć
ćć®ćć”ć¤ć«ć«ć²ć¼ć ć®ē¶ę ćčæ½å ćć¾ćć ćć”ć¤ć«ć®å é ć«ććReSwiftćć¤ć³ćć¼ćććććØććå§ćć¾ćć
import ReSwift
ꬔć«ććć”ć¤ć«ć®ęå¾ć«ę¬”ć®ć³ć¼ććčæ½å ćć¾ćć
struct GameState: StateType { var memoryCards: [MemoryCard] // 1 var showLoading: Bool // 2 var gameFinished: Bool }
ććć«ćććć²ć¼ć ć®ē¶ę ćę±ŗć¾ćć¾ććä½æēØåÆč½ćŖć”ć¢ćŖć¼ć«ć¼ćć®é åć®å 容ć«å ćć¦ććć©ć”ć¼ćæć¼ćęå®ćć¾ćć
- ćć¦ć³ćć¼ćć¤ć³ćøć±ć¼ćæćč”Øē¤ŗć¾ććÆéč”Øē¤ŗć
- ć²ć¼ć ćŖć¼ćć¼ć§ćć
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) { // 1 case _ as FetchTunesAction: state = GameState(memoryCards: [], showLoading: true, gameFinished: false) // 2 case let setCardsAction as SetCardsAction: state.memoryCards = generateNewCards(with: setCardsAction.cardImageUrls) state.showLoading = false default: break } return state
ē¶ę ćå®ę°ć«å¤ę“ćć¦ććć¢ćÆć·ć§ć³ć¹ć¤ćććčµ·åććꬔć®ć¢ćÆć·ć§ć³ćå®č”ćć¾ćć
- FetchTunesActionćÆshowLoadingćtrueć«čØå®ćć¾ćć
- SetCardsActionć§ćÆćć«ć¼ććć©ć³ćć ć«éøęćććshowLoadingćfalseć«čØå®ććć¾ććgenerateNewCardsćÆćMemoryGameLogic.swiftćć”ć¤ć«ļ¼ć¹ćæć¼ćæć¼ćććøć§ćÆćć«å«ć¾ćć¦ćććć”ć¤ć«ļ¼ć«ććć¾ćć
GameViewControllerć§ć«ć¼ććć¬ć¤ć¢ć¦ććć¾ććć»ć«ćä½ęććććØććå§ćć¾ććCardCollectionViewCell.swift
ćć”ć¤ć«ćéććꬔć®ć”ć½ćććCardCollectionViewCellć®ęå¾ć«čæ½å ćć¾ćć
func configureCell(with cardState: MemoryCard) { let url = URL(string: cardState.imageUrl) // 1 cardImageView.kf.setImage(with: url) // 2 cardImageView.alpha = cardState.isAlreadyGuessed || cardState.isFlipped ? 1 : 0 }
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() { // 1 store.dispatch(fetchTunes) collectionView.delegate = self loadingIndicator.hidesWhenStopped = true // 2 collectionDataSource = CollectionDataSource(cellIdentifier: "CardCell", models: [], configureCell: { (cell, model) -> CardCollectionViewCell in cell.configureCell(with: model) return cell }) collectionView.dataSource = collectionDataSource }
StoreSubscriberćéøęććć¾ć§ćććć«ććććć¤ćć®ć³ć³ćć¤ć©č¦åćēŗēććććØć«ę³Øęćć¦ćć ććććć„ć¼ćÆćviewWillAppearć®gameStateć«ćµćć¹ćÆć©ć¤ćććviewWillDisappearć®ćµćć¹ćÆć©ć¤ććč§£é¤ćć¾ććviewDidLoadć§ćÆćꬔć®ć¢ćÆć·ć§ć³ćå®č”ććć¾ćć
- FetchTunesćå®č”ćććiTunes APIććć®ē»åć®åäæ”ćéå§ććć¾ćć
- ć»ć«ćÆćconfigureCellć®é©åćŖć¢ćć«ćåå¾ććCollectionDataSourcećä½æēØćć¦ę§ęććć¾ćć
ꬔć«ćStoreSubscriberćå®č”ććććć®ę”å¼µę©č½ćčæ½å ććåæ č¦ćććć¾ćććć”ć¤ć«ć®ęå¾ć«ę¬”ćčæ½å ćć¾ćć
// MARK: - StoreSubscriber extension GameViewController: StoreSubscriber { func newState(state: GameState) { collectionDataSource?.models = state.memoryCards collectionView.reloadData() // 1 state.showLoading ? loadingIndicator.startAnimating() : loadingIndicator.stopAnimating() // 2 if state.gameFinished { showGameFinishedAlert() store.dispatch(fetchTunes) } } }
ććć«ćććē¶ę ć®å¤ę“ćå¦ēććććć«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ć«é¢ććę°ććććććÆćåćäøćć¦ćć¾ćć
ćč³Ŗåćć³ć”ć³ććć¢ć¤ćć¢ćććå “åćÆć仄äøć®ćć©ć¼ć©ć ć®ćć£ć¹ć«ćć·ć§ć³ć«åå ćć¦ćć ććļ¼