iOS: Solved
Redux on iOS gots some problems
Redux Problems
Redux Problems
Redux Problems
What's a view controller?
struct MyViewModel {
let viewState: String
// ...
}
protocol MyHandler: class {
func doSomething()
// ...
}
final class MyViewController: UIViewController {
weak var handler: MyHandler!
func inject(handler handler: MyHandler) {
self.handler = handler
}
func render(viewModel: MyViewModel) {
// ...
}
@IBAction func doSomething(sender: AnyObject) {
handler.doSomething()
}
}
Nothing else?!?
App Coordinators
App Coordinators
App Coordinators
Redux & App Coordinators
App Coordinators & Redux
App coordinators instantiate view controllers
App coordinators instantiate view controllers
& subscribe them to the store
App coordinators handle user actions
App coordinators handle user actions
& dispatch data actions to the store
App coordinators perform navigation
App coordinators perform navigation
via the app state's route
public protocol Coordinator: class {
var route: Route { get set }
var rootViewController: UIViewController { get }
func start()
}
init(allYourDependencies: ...)
Media App Example
Route
Scene Coordinators
Tab Bar Scenes
Navigation Controller Scenes
Framework Help
public protocol SceneCoordinator: Coordinator {
var scenePrefix: String { get }
var currentScene: Coordinator? { get }
func changeScene(route: Route)
func sceneRoute(route: Route) -> Route
}
public extension SceneCoordinator {
public var route: Route {
get {
// ...
}
set {
// ...
}
}
// ...
}
Framework Help
public protocol SceneCoordinator: Coordinator {
var scenePrefix: String { get }
var currentScene: Coordinator? { get }
func changeScene(route: Route)
func sceneRoute(route: Route) -> Route
}
public extension SceneCoordinator {
public var route: Route {
get {
// ...
}
set {
// ...
}
}
// ...
}
public protocol TabBarControllerCoordinator: SceneCoordinator, UITabBarControllerDelegate {
// ...
}
public extension TabBarControllerCoordinator {
// ...
}
public protocol NavigationControllerCoordinator: Coordinator {
// ...
}
public extension NavigationControllerCoordinator {
// ...
}
Challenges
Subscribing view controllers
Challenges
public protocol Renderer: SubscriberType {
associatedtype ViewModel
func render(viewModel: ViewModel)
}
extension Renderer {
public func newState(state: Any) {
if let viewModel = state as? ViewModel {
render(viewModel)
} else {
preconditionFailure("render(_:) does not accept right type")
}
}
}
extension SignInViewController: Renderer {}
extension CatalogViewController: Renderer {}
Challenges
App State → View Model
Challenges
extension SignInViewModel {
init(_ state: AppState) {
name = state.name
}
}
store.subscribe(signInViewController, SignInViewModel.init)
Challenges
MovieViewController → Show V for Vendetta
Challenges
extension MovieViewModel {
static func createForMovie(id: Identifier) -> (AppState) -> (MovieViewModel) {
return { appState in
// ...
}
}
}
store.subscribe(movieViewController, MovieViewModel.createForMovie(id))
Challenges
Lifecycle Hooks
Subscribe to store onviewDidLoad
at earliest
Challenges
extension UIViewController {
public class func swizzleLifecycleDelegatingViewControllerMethods() {
dispatch_once(&Static.token) {
cordux_swizzleMethod(#selector(viewDidLoad),
swizzled: #selector(cordux_viewDidLoad))
}
}
func cordux_viewDidLoad() {
self.cordux_viewDidLoad()
guard let vc = self as? ViewController else {
return
}
vc.context?.lifecycleDelegate?.viewDidLoad?(viewController: self)
}
}
Challenges
public protocol ViewController: class {
var context: Context? { get }
}
public final class Context: NSObject {
public weak var lifecycleDelegate: ViewControllerLifecycleDelegate?
public init(_ lifecycleDelegate: ViewControllerLifecycleDelegate?) {
self.lifecycleDelegate = lifecycleDelegate
}
}
Challenges
public extension UIViewController {
public var context: Context? {
get {
return objc_getAssociatedObject(self, &Keys.Context) as? Context
}
set {
if let newValue = newValue {
objc_setAssociatedObject(
self,
&Keys.Context,
newValue as Context?,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
)
}
}
}
Challenges
signInViewController.context = Context(lifecycleDelegate: self)
extension AuthenticationCoordinator: ViewControllerLifecycleDelegate {
@objc func viewDidLoad(viewController viewController: UIViewController) {
if viewController === signInViewController {
store.subscribe(signInViewController, SignInViewModel.init)
}
}
}
Challenges
Route with View Controller
movies/v-for-vendettaChallenges
public final class Context: NSObject {
public let routeSegment: RouteConvertible
public weak var lifecycleDelegate: ViewControllerLifecycleDelegate?
public init(_ routeSegment: RouteConvertible,
lifecycleDelegate: ViewControllerLifecycleDelegate?) {
self.routeSegment = routeSegment
self.lifecycleDelegate = lifecycleDelegate
}
}
Challenges
Updating Route
pop vc from nav, tap on tabChallenges
public final class Store<State : StateType>: StoreType {
public func setRoute<T>(action: RouteAction<T>) {
state.route = reduce(action, route: state.route)
}
public func route<T>(action: RouteAction<T>) {
state.route = reduce(action, route: state.route)
dispatch(action)
}
public func dispatch(action: Action) {
state = reducer._handleAction(action, state: state) as! State
subscriptions.forEach {
$0.subscriber?._newState($0.transform?(state) ?? state)
}
}
// ...
}