ReSwift恮ä½æē”Øļ¼ščØ˜ę†¶ć‚²ćƒ¼ćƒ ć‚¢ćƒ—ćƒŖć‚±ćƒ¼ć‚·ćƒ§ćƒ³ć®ä½œęˆ

ćƒ¬ć‚¹ć‚¦ć‚£ćƒ•ćƒˆ






恔ę³Øꄏ 恓恮čؘäŗ‹ć§ćÆXcode 8ćØSwift 3悒ä½æē”Øć—ć¾ć™ć€‚



iOSć‚¢ćƒ—ćƒŖć‚±ćƒ¼ć‚·ćƒ§ćƒ³ć®ć‚µć‚¤ć‚ŗćŒå¢—åŠ ć—ē¶šć‘ć‚‹ćØ态 MVC惑ć‚æćƒ¼ćƒ³ćÆć€Œé©åˆ‡ćŖć€ć‚¢ćƒ¼ć‚­ćƒ†ć‚Æćƒćƒ£ć‚½ćƒŖćƒ„ćƒ¼ć‚·ćƒ§ćƒ³ćØć—ć¦ć®å½¹å‰²ć‚’å¾ć€…ć«å¤±ć„ć¾ć™ć€‚



iOS開ē™ŗč€…å‘ć‘ć«ćÆ态MVVM态VIPER态 RibletsćŖć©ć€ć‚ˆć‚ŠåŠ¹ęžœēš„ćŖć‚¢ćƒ¼ć‚­ćƒ†ć‚Æćƒćƒ£ćƒ‘ć‚æćƒ¼ćƒ³ć‚’åˆ©ē”Øć§ćć¾ć™ć€‚ 恓悌悉ćÆ非åøø恫ē•°ćŖć‚Šć¾ć™ćŒć€å…±é€šć®ē›®ēš„ćŒć‚ć‚Šć¾ć™ć€‚å¤šę–¹å‘ć®ćƒ‡ćƒ¼ć‚æć‚¹ćƒˆćƒŖćƒ¼ćƒ ć§å˜äø€ć®č²¬ä»»ć®åŽŸå‰‡ć«åŸŗć„ć„ć¦ć‚³ćƒ¼ćƒ‰ć‚’ćƒ–ćƒ­ćƒƒć‚Æć«åˆ†å‰²ć™ć‚‹ć“ćØ恧恙怂 å¤šę–¹å‘ć‚¹ćƒˆćƒŖćƒ¼ćƒ ć§ćÆć€ćƒ‡ćƒ¼ć‚æćÆē•°ćŖć‚‹ćƒ¢ć‚øćƒ„ćƒ¼ćƒ«é–“ć§ē•°ćŖć‚‹ę–¹å‘ć«ē§»å‹•ć—ć¾ć™ć€‚



å “åˆć«ć‚ˆć£ć¦ćÆć€å¤šę–¹å‘ćƒ‡ćƒ¼ć‚æć‚¹ćƒˆćƒŖćƒ¼ćƒ ć‚’ä½æē”Ø恗恟恏ćŖ恄ļ¼ˆć¾ćŸćÆåæ…要ćŖ恄ļ¼‰å “åˆćŒć‚ć‚Šć¾ć™ć€‚ä»£ć‚ć‚Šć«ć€ćƒ‡ćƒ¼ć‚æ悒äø€ę–¹å‘恫送äæ”恙悋åæ…č¦ćŒć‚ć‚Šć¾ć™ć€‚ć“ć‚ŒćÆå˜ę–¹å‘ćƒ‡ćƒ¼ć‚æć‚¹ćƒˆćƒŖćƒ¼ćƒ ć§ć™ć€‚ ReSwiftć«é–¢ć™ć‚‹ć“ć®čؘäŗ‹ć§ćÆć€ę‰“ćŸć‚ŒćŸćƒˆćƒ©ćƒƒć‚Æ悒ć‚Ŗćƒ•ć«ć—ć€ MemoryTunesćØ恄恆Memory Gameć‚¢ćƒ—ćƒŖć‚±ćƒ¼ć‚·ćƒ§ćƒ³ć‚’ä½œęˆć™ć‚‹ćØćć«ReSwiftćƒ•ćƒ¬ćƒ¼ćƒ ćƒÆćƒ¼ć‚Æ悒ä½æē”Øć—ć¦å˜ę–¹å‘ćƒ‡ćƒ¼ć‚æć‚¹ćƒˆćƒŖćƒ¼ćƒ ć‚’å®Ÿč£…ć™ć‚‹ę–¹ę³•ć‚’å­¦ēæ’ć—ć¾ć™ć€‚



ć—ć‹ć—ć€ęœ€åˆć«ć€ReSwiftćØćÆä½•ć§ć™ć‹ļ¼Ÿ



ReSwift恮ꦂ要



ReSwiftćÆ态Swift恧Reduxć‚¢ćƒ¼ć‚­ćƒ†ć‚Æćƒćƒ£ć‚’å®Ÿč£…ć™ć‚‹ć®ć«å½¹ē«‹ć¤å°ć•ćŖćƒ•ćƒ¬ćƒ¼ćƒ ćƒÆćƒ¼ć‚Æ恧恙怂



ReSwift恫ćÆ4恤恮äø»č¦ćŖć‚³ćƒ³ćƒćƒ¼ćƒćƒ³ćƒˆćŒć‚ć‚Šć¾ć™ć€‚





ReSwift恫ćÆå¤šćć®čˆˆå‘³ę·±ć„åˆ©ē‚¹ćŒć‚ć‚Šć¾ć™ć€‚





å¤šę–¹å‘ć¾ćŸćÆå˜ę–¹å‘ćƒ•ćƒ­ćƒ¼



ćƒ‡ćƒ¼ć‚æćƒ•ćƒ­ćƒ¼ć®ę„å‘³ć‚’ę˜Žē¢ŗć«ć™ć‚‹ćŸć‚ć«ć€ę¬”ć®ä¾‹ć‚’ē¤ŗć—ć¾ć™ć€‚ VIPER悒ä½æē”Øć—ć¦ä½œęˆć•ć‚ŒćŸć‚¢ćƒ—ćƒŖć‚±ćƒ¼ć‚·ćƒ§ćƒ³ćÆć€ćƒ¢ć‚øćƒ„ćƒ¼ćƒ«é–“ć®å¤šę–¹å‘ćƒ‡ćƒ¼ć‚æćƒ•ćƒ­ćƒ¼ć‚’ć‚µćƒćƒ¼ćƒˆć—ć¾ć™ć€‚



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ć‚Æćƒ©ć‚¹ć‚’ę›“ę–°ć—ć€ę‹”å¼µę©Ÿčƒ½ć‚’čæ½åŠ ć—ć¾ć—ćŸć€‚ ē§ćŸć”ćŒć‚„ć£ćŸć“ćØć‚’č©³ć—ćč¦‹ć¦ćæć¾ć—ć‚‡ć†ļ¼š



  1. AppStatećŒć‚°ćƒ­ćƒ¼ćƒćƒ«ć‚¹ćƒˆć‚¢ć«ć‚µćƒ–ć‚¹ć‚Æćƒ©ć‚¤ćƒ–ć•ć‚Œć¾ć—ćŸć€‚ ć‚Æćƒ­ćƒ¼ć‚øćƒ£ćƒ¼å¼ć§ć€selectćÆ态routingStateć®å¤‰ę›“ć«ć‚µćƒ–ć‚¹ć‚Æćƒ©ć‚¤ćƒ–ć—ć¦ć„ć‚‹ć“ćØ悒ē¤ŗć—ć¾ć™ć€‚
  2. pushViewControllerćÆć€ć‚¤ćƒ³ć‚¹ć‚æćƒ³ć‚¹åŒ–ć—ć¦ćƒŠćƒ“ć‚²ćƒ¼ć‚·ćƒ§ćƒ³ć‚¹ć‚æ惃ć‚Æ恫čæ½åŠ ć™ć‚‹ćŸć‚ć«ä½æē”Øć•ć‚Œć¾ć™ć€‚ ęø”ć•ć‚ŒćŸč­˜åˆ„å­ć«åŸŗć„ć„ć¦ć‚³ćƒ³ćƒˆćƒ­ćƒ¼ćƒ©ćƒ¼ć‚’ćƒ­ćƒ¼ćƒ‰ć™ć‚‹instantiateViewControllerćƒ”ć‚½ćƒƒćƒ‰ć‚’ä½æē”Øć—ć¾ć™ć€‚
  3. routingStatećŒå¤‰ę›“ć•ć‚Œć‚‹ćØ恙恐恫newStatećŒć‚³ćƒ¼ćƒ«ćƒćƒƒć‚Æć‚’å—ć‘å–ć‚‹ć‚ˆć†ć«ć€ StoreSubscriberćØäø€č‡“恙悋AppRouterć‚’ä½œęˆć—ć¾ć™ć€‚
  4. ćƒ«ćƒ¼ćƒˆView Controller恫電ęŗć‚’ä¾›ēµ¦ć—ćŸććŖ恄恮恧态ē¾åœØć®å®›å…ˆćŒćƒ«ćƒ¼ćƒˆć§ć‚ć‚‹ć‹ć©ć†ć‹ć‚’ē¢ŗčŖć—ć¦ćć ć•ć„ć€‚
  5. ēŠ¶ę…‹ćŒå¤‰åŒ–ć—ćŸć‚‰ć€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ć®å¤‰ę›“ć‚’ć‚µćƒ–ć‚¹ć‚Æćƒ©ć‚¤ćƒ–ć—ć€ćƒ¦ćƒ¼ć‚¶ćƒ¼ć‚¤ćƒ³ć‚æćƒ¼ćƒ•ć‚§ć‚¤ć‚¹ć«å®£č؀ēš„恫ēŠ¶ę…‹ć‚’č”Øē¤ŗć—ć¾ć™ć€‚



  1. TableDataSourcećÆć‚¹ć‚æćƒ¼ćƒˆć‚¢ćƒƒćƒ—ć‚·ć‚¹ćƒ†ćƒ ć«å«ć¾ć‚Œć¦ćŠć‚Šć€UITableViewć®å®£čØ€åž‹ćƒ‡ćƒ¼ć‚æć‚½ćƒ¼ć‚¹ćØć—ć¦ę©Ÿčƒ½ć—ć¾ć™ć€‚
  2. viewWillAppear恧menuStateć‚’ć‚µćƒ–ć‚¹ć‚Æćƒ©ć‚¤ćƒ–ć—ć¾ć™ć€‚ menuStatećŒå¤‰ę›“ć•ć‚Œć‚‹ćŸć³ć«ć€newStateć§ć‚³ćƒ¼ćƒ«ćƒćƒƒć‚Æć‚’å—ć‘å–ć‚‹ć‚ˆć†ć«ćŖć‚Šć¾ć™ć€‚
  3. åæ…要恫åæœć˜ć¦ć€ē™»éŒ²ć‚’č§£é™¤ć—ć¾ć™ć€‚
  4. 恓悌ćÆ宣č؀éƒØ恧恙怂 恓恓恧态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恫äæå­˜ć—ć¾ć™ć€‚ ć“ć‚ŒćŒčµ·ć“ć‚‹ę–¹ę³•ć®č©³ē“°ļ¼š



  1. ē¾åœØć®ć‚«ćƒ†ć‚“ćƒŖćÆć€åˆ©ē”ØåÆčƒ½ć§ć‚ć‚Œć°UserDefaults恋悉čŖ­ćæč¾¼ć¾ć‚Œć€ć¾ć ä½œęˆć•ć‚Œć¦ć„ćŖć„å “åˆćÆCategoriesStateć‚¤ćƒ”ćƒ¼ć‚øć®ä½œęˆć«ä½æē”Øć•ć‚Œć¾ć™ć€‚
  2. ChangeCategoryAction恫åæœē­”恗恦态ēŠ¶ę…‹ć‚’ꛓꖰ恗态UserDefaultsć«ę–°ć—ć„ć‚«ćƒ†ć‚“ćƒŖ悒äæå­˜ć—ć¾ć™ć€‚
  3. getCurrentCategoryStateFromUserDefaultsćÆ态UserDefaultsć‹ć‚‰ć‚«ćƒ†ć‚“ćƒŖ悒čŖ­ćæč¾¼ć‚€ćƒ˜ćƒ«ćƒ‘ćƒ¼é–¢ę•°ć§ć™ć€‚
  4. 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ć«éžåøøć«ä¼¼ć¦ć„ć¾ć™ć€‚ ćƒć‚¤ćƒ©ć‚¤ćƒˆćÆꬔ恮ćØ恊悊恧恙怂



  1. viewWillAppear恧态categoryStateć®å¤‰ę›“ć‚’ć‚µćƒ–ć‚¹ć‚Æćƒ©ć‚¤ćƒ–ć—ć€viewWillDisappearć§ć‚µćƒ–ć‚¹ć‚Æćƒ©ć‚¤ćƒ–ć‚’č§£é™¤ć—ć¾ć™ć€‚
  2. ćƒ¦ćƒ¼ć‚¶ćƒ¼ćŒć‚»ćƒ«ć‚’éøꊞ恙悋ćØ态ChangeCategoryActionćŒå‘¼ć³å‡ŗć•ć‚Œć¾ć™ć€‚
  3. 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 }
      
      





ć“ć‚Œć«ć‚ˆć‚Šć€ć‚²ćƒ¼ćƒ ć®ēŠ¶ę…‹ćŒę±ŗć¾ć‚Šć¾ć™ć€‚ä½æē”ØåÆčƒ½ćŖćƒ”ćƒ¢ćƒŖćƒ¼ć‚«ćƒ¼ćƒ‰ć®é…åˆ—ć®å†…å®¹ć«åŠ ćˆć¦ć€ćƒ‘ćƒ©ćƒ”ćƒ¼ć‚æćƒ¼ć‚’ęŒ‡å®šć—ć¾ć™ć€‚



  1. ćƒ€ć‚¦ćƒ³ćƒ­ćƒ¼ćƒ‰ć‚¤ćƒ³ć‚øć‚±ćƒ¼ć‚æ态č”Øē¤ŗć¾ćŸćÆ非č”Øē¤ŗ怂
  2. ć‚²ćƒ¼ćƒ ć‚Ŗćƒ¼ćƒćƒ¼ć§ć™ć€‚


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
      
      





ēŠ¶ę…‹ć‚’å®šę•°ć«å¤‰ę›“ć—ć¦ć‹ć‚‰ć‚¢ć‚Æć‚·ćƒ§ćƒ³ć‚¹ć‚¤ćƒƒćƒć‚’čµ·å‹•ć—ć€ę¬”ć®ć‚¢ć‚Æć‚·ćƒ§ćƒ³ć‚’å®Ÿč”Œć—ć¾ć™ć€‚



  1. FetchTunesActionćÆshowLoading悒true恫čØ­å®šć—ć¾ć™ć€‚
  2. 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ćƒ”ć‚½ćƒƒćƒ‰ćÆć€ę¬”ć®ć‚¢ć‚Æć‚·ćƒ§ćƒ³ć‚’å®Ÿč”Œć—ć¾ć™ć€‚

  1. ē”»åƒć‚’ć‚­ćƒ£ćƒƒć‚·ćƒ„ć™ć‚‹ćŸć‚ć«å„Ŗ悌恟Kingfisherćƒ©ć‚¤ćƒ–ćƒ©ćƒŖ悒ä½æē”Øć—ć¾ć™ć€‚
  2. ęŽØęø¬ć•ć‚Œć‚‹ć‹ć€é€†ć•ć¾ć«ćŖć£ćŸć‚«ćƒ¼ćƒ‰ć‚’č”Øē¤ŗć—ć¾ć™ć€‚


ę¬”ć«ć€ć‚³ćƒ¬ć‚Æć‚·ćƒ§ćƒ³ćƒ“ćƒ„ćƒ¼ć‚’å®Ÿč”Œć—ć¦ćƒžćƒƒćƒ—ć‚’č”Øē¤ŗć—ć¾ć™ć€‚ćƒ†ćƒ¼ćƒ–ćƒ«ćƒ“ćƒ„ćƒ¼ćØåŒę§˜ć«ć€ä½æē”Øć—ć¦ć„ć‚‹ćƒ©ćƒ³ćƒćƒ£ćƒ¼ć«å«ć¾ć‚Œć¦ć„ć‚‹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恧ćÆć€ę¬”ć®ć‚¢ć‚Æć‚·ćƒ§ćƒ³ćŒå®Ÿč”Œć•ć‚Œć¾ć™ć€‚



  1. FetchTunesćŒå®Ÿč”Œć•ć‚Œć€iTunes API恋悉恮ē”»åƒć®å—äæ”ćŒé–‹å§‹ć•ć‚Œć¾ć™ć€‚
  2. ć‚»ćƒ«ćÆ态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ćŒć‚¢ć‚Æćƒ†ć‚£ćƒ–ć«ćŖć‚Šć¾ć™ć€‚ćƒ‡ćƒ¼ć‚æć‚½ćƒ¼ć‚¹ćŒę›“ę–°ć•ć‚Œć‚‹ć ć‘ć§ćŖ恏态



  1. ćƒ€ć‚¦ćƒ³ćƒ­ćƒ¼ćƒ‰ć‚¤ćƒ³ć‚øć‚±ćƒ¼ć‚æć®ć‚¹ćƒ†ćƒ¼ć‚æć‚¹ćÆć€ć‚¹ćƒ†ćƒ¼ć‚æć‚¹ć«åæœć˜ć¦ę›“ę–°ć•ć‚Œć¾ć™ć€‚
  2. ć‚²ćƒ¼ćƒ ć‚’å†čµ·å‹•ć—ć€ć‚²ćƒ¼ćƒ ć‚Ŗćƒ¼ćƒćƒ¼ę™‚ć«č­¦å‘Šć‚’č”Øē¤ŗć—ć¾ć™ć€‚


ć‚²ćƒ¼ćƒ ć‚’ć‚³ćƒ³ćƒ‘ć‚¤ćƒ«ć—ć¦å®Ÿč”Œć—ć€[ ę–°ć—ć„ć‚²ćƒ¼ćƒ ]悒éøꊞ恙悋ćØ态惞惃惗恌č”Øē¤ŗć•ć‚Œć¾ć™ć€‚



ćƒ—ćƒ¬ć‚¤ć™ć‚‹



ć‚²ćƒ¼ćƒ ć®ćƒ­ć‚ø惃ć‚Æć«ć‚ˆć‚Šć€ćƒ—ćƒ¬ćƒ¼ćƒ¤ćƒ¼ćÆ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ć«ć¤ć„ć¦å­¦ć¶ć¹ćć“ćØćŒć¾ć ćŸćć•ć‚“ć‚ć‚Šć¾ć™ć€‚





恂ćŖ恟ćÆć“ć®ćƒ†ćƒ¼ćƒžć«é–¢ć™ć‚‹ć‚ćŖćŸć®ēŸ„č­˜ć‚’å±•é–‹ć—ćŸć„å “åˆćÆ态聞恏ReSwift恮話悒 -č©±å††åŗ§ćƒ™ćƒ³ć‚øćƒ£ćƒŸćƒ³ļ¼ˆćƒ™ćƒ³ć‚øćƒ£ćƒŸćƒ³ENCZļ¼‰ć€ć‚ÆćƒŖć‚Øćƒ¼ć‚æćƒ¼ReSwift



恧ReSwiftć®ćƒ—ćƒ­ć‚ø悧ć‚Æćƒˆć®čˆˆå‘³ę·±ć„ä¾‹ć®ć‚‚ćŸćć•ć‚“ć€‚ęœ€å¾Œć«ć€Christian Tietzeć®ćƒ–ćƒ­ć‚°ć§ćÆ态ReSwiftć«é–¢ć™ć‚‹ę–°ć—ć„ćƒˆćƒ”ćƒƒć‚Æć‚’å–ć‚ŠäøŠć’ć¦ć„ć¾ć™ć€‚



恔č³Ŗå•ć€ć‚³ćƒ”ćƒ³ćƒˆć€ć‚¢ć‚¤ćƒ‡ć‚¢ćŒć‚ć‚‹å “åˆćÆ态仄äø‹ć®ćƒ•ć‚©ćƒ¼ćƒ©ćƒ ć®ćƒ‡ć‚£ć‚¹ć‚«ćƒƒć‚·ćƒ§ćƒ³ć«å‚åŠ ć—ć¦ćć ć•ć„ļ¼



All Articles