Model-View-ViewModel. • Models: raw representation of data from the backend • Views: The actual UI. Views are driven by view models and keep no reference of Models. • ViewModels: Where the magic happens. ViewModels are responsible for state keeping and for driving the views. Other architectural patterns that apply on iOS are: MVC (Model-View-Controller) the default pattern on iOS, sometimes it degrades to Massive View Controller, VIPER
Similar to Rx(Java, Swift, JS, etc) though tailored to Apple’s platforms. • Can be used to uniformly represent common Cocoa and generic programming patterns (delegates, notifications, closures). • Less spaghetti code. • Steep learning curve (though it’s worth it). • Core Concepts: Signal, SignalProducer, Property, Action. (We will be using most of them during this session). Further Reading: 1. https://github.com/ReactiveCocoa/ReactiveSwift 2. https://rxmarbles.com/
over time that can be observed. It is generally used to represent streams of values that are already “in progress” like notifications or user input. • SignalProducer: used to represent operations or tasks e.g network requests. When a SignalProducer starts a new task/operation will be created allowing the caller to observe the results.
over time that can be observed. It is generally used to represent streams of values that are already “in progress” like notifications or user input. • SignalProducer: used to represent operations or tasks e.g network requests. When a SignalProducer starts a new task/operation will be created allowing the caller to observe the results. • Property: stores a value and notifies observers about future changes to that value. Properties and MutableProperties can act as a bridge between the reactive and the imperative worlds (though most of the time it is better to use signals).
over time that can be observed. It is generally used to represent streams of values that are already “in progress” like notifications or user input. • SignalProducer: used to represent operations or tasks e.g network requests. When a SignalProducer starts a new task/operation will be created allowing the caller to observe the results. • Property: stores a value and notifies observers about future changes to that value. Properties and MutableProperties can act as a bridge between the reactive and the imperative worlds (though most of the time it is better to use signals). • Action: will do some work when executed with an input. While executing, zero or more output values and/or a failure may be generated. Actions are useful for performing side-effecting work upon user interaction, like when a button is clicked. Actions can also be automatically disabled based on a property, and this disabled state can be represented in a UI by disabling any controls associated with the action. Further Reading: 1. https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Documentation/FrameworkOverview.md
2. Learn how to perform requests and parsing responses using ReactiveSwift and generics. 3. Learn how to extend Signals and SignalProducers. 4. Understand ReactiveFeedback and how to create a ViewModel using it. 5. Learn how to create custom bindings. 6. Do this under 45mins.
pod init 2. Open the generated Podfile. 3. Add ReactiveCocoa: Useful bindings for UIKit 4. Add ReactiveSwift. 5. Add ReactiveFeedback (more on that later). 6. Run: pod install
the box affordances for many of the classes we use every day, one such class being URLSession. All this functionality is given to the user under the namespace `reactive`.
the box affordances for many of the classes we use every day, one such class being URLSession. All this functionality is given to the user under the namespace `reactive`. Let’s see how to fetch some data from an endpoint using URLSession and ReactiveSwift.
the box affordances for many of the classes we use every day, one such class being URLSession. All this functionality is given to the user under the namespace `reactive`. Let’s see how to fetch some data from an endpoint using URLSession and ReactiveSwift. URLSession.
the box affordances for many of the classes we use every day, one such class being URLSession. All this functionality is given to the user under the namespace `reactive`. Let’s see how to fetch some data from an endpoint using URLSession and ReactiveSwift. URLSession.shared.
the box affordances for many of the classes we use every day, one such class being URLSession. All this functionality is given to the user under the namespace `reactive`. Let’s see how to fetch some data from an endpoint using URLSession and ReactiveSwift. URLSession.shared.reactive
the box affordances for many of the classes we use every day, one such class being URLSession. All this functionality is given to the user under the namespace `reactive`. Let’s see how to fetch some data from an endpoint using URLSession and ReactiveSwift. URLSession.shared.reactive.data(with: request)
the box affordances for many of the classes we use every day, one such class being URLSession. All this functionality is given to the user under the namespace `reactive`. Let’s see how to fetch some data from an endpoint using URLSession and ReactiveSwift. let producer = URLSession.shared.reactive.data(with: request)
the box affordances for many of the classes we use every day, one such class being URLSession. All this functionality is given to the user under the namespace `reactive`. Let’s see how to fetch some data from an endpoint using URLSession and ReactiveSwift. let producer = URLSession.shared.reactive.data(with: request) This producer though is of type SignalProducer<(Data, URLResponse), Error> and we need posts, not data.
the box affordances for many of the classes we use every day, one such class being URLSession. All this functionality is given to the user under the namespace `reactive`. Let’s see how to fetch some data from an endpoint using URLSession and ReactiveSwift. let producer = URLSession.shared.reactive.data(with: request) This producer though is of type SignalProducer<(Data, URLResponse), Error> and we need posts, not data. What can we do?
applies a transformation and returns the resulting output. It also happens to be supported by ReactiveSwift. Decoding is provider by Swift Standard Library on Decobable objects (Post happens to conform to Codable).
applies a transformation and returns the resulting output. It also happens to be supported by ReactiveSwift. Decoding is provider by Swift Standard Library on Decobable objects (Post happens to conform to Codable). Decoding might also throw errors.
applies a transformation and returns the resulting output. It also happens to be supported by ReactiveSwift. Decoding is provider by Swift Standard Library on Decobable objects (Post happens to conform to Codable). Decoding might also throw errors. map does not handle errors...
applies a transformation and returns the resulting output. It also happens to be supported by ReactiveSwift. Decoding is provider by Swift Standard Library on Decobable objects (Post happens to conform to Codable). Decoding might also throw errors. map does not handle errors… but attemptMap does.
applies a transformation and returns the resulting output. It also happens to be supported by ReactiveSwift. Decoding is provider by Swift Standard Library on Decobable objects (Post happens to conform to Codable). Decoding might also throw errors. map does not handle errors… but attemptMap does. attemptMap takes an input, applies a transformation that might fail and returns an output in the form of Swift.Result<Output, Error>
applies a transformation and returns the resulting output. It also happens to be supported by ReactiveSwift. Decoding is provider by Swift Standard Library on Decobable objects (Post happens to conform to Codable). Decoding might also throw errors. map does not handle errors… but attemptMap does. attemptMap takes an input, applies a transformation that might fail and returns an output in the form of Swift.Result<Output, Error> ReactiveSwift intercepts that result, decomposes it and forwards the output or fails with error.
request) .mapError(CoreError.unknown) // Convert errors to our own error enum. .attemptMap { (data, _) -> Result<[Post], CoreError> in do { let decoded = try JSONDecoder().decode([Post].self, from: data) return .success(decoded) // If successful, return the posts. } catch let err { return .failure(.unknown(err)) // If failed, return the error. } }
we were to use the same approach for many different requests, it would mean that we would have to right a lot of boilerplate code. Can we do better? Maybe if we wrote an extension to SignalProducer.
we were to use the same approach for many different requests, it would mean that we would have to right a lot of boilerplate code. Can we do better? Maybe if we wrote an extension to SignalProducer. And maybe if we did it in such a way that it will work for every decodable.
lift. lift is a handy function that elevates signal operators to work on signal producers. Which means… If we write this... extension SignalProducer where Value == (Data, URLResponse) { func decode<T: Decodable>(_ type: T.Type) -> SignalProducer<T, CoreError> { lift { $0.decode(type) } } }
we can reuse and apply to every Decodable we might encounter in the future. For this presentation, we won’t need it any more. This code, is part of a class called PostProvider, responsible for fetching posts.
is tight coupling. To alleviate this, Swift strongly suggests we use protocols. PostProvider conforms to a protcol, originally named: PostProviderProtocol.
is tight coupling. To alleviate this, Swift strongly suggests we use protocols. PostProvider conforms to a protcol, originally named: PostProviderProtocol. public protocol PostProviderProtocol: class { func fetchPosts(page: Int, limit: Int) -> SignalProducer<[Post], CoreError> }
is tight coupling. To alleviate this, Swift strongly suggests we use protocols. PostProvider conforms to a protcol, originally named: PostProviderProtocol. public protocol PostProviderProtocol: class { func fetchPosts(page: Int, limit: Int) -> SignalProducer<[Post], CoreError> } Every ViewModel that wants to fetch posts, will interact with PostProviderProtocol and not directly with PostProvider.
Our code has to manage a lot of state e.g. server responses, cached data, UI state, routing etc. Some may say that Reactive Programming can help us a lot but, in the wrong hands, it can do even more harm to your code base.
Our code has to manage a lot of state e.g. server responses, cached data, UI state, routing etc. Some may say that Reactive Programming can help us a lot but, in the wrong hands, it can do even more harm to your code base. The goal of this library is to provide a simple and intuitive approach to designing reactive state machines.
Our code has to manage a lot of state e.g. server responses, cached data, UI state, routing etc. Some may say that Reactive Programming can help us a lot but, in the wrong hands, it can do even more harm to your code base. The goal of this library is to provide a simple and intuitive approach to designing reactive state machines. Core Concepts:
Our code has to manage a lot of state e.g. server responses, cached data, UI state, routing etc. Some may say that Reactive Programming can help us a lot but, in the wrong hands, it can do even more harm to your code base. The goal of this library is to provide a simple and intuitive approach to designing reactive state machines. Core Concepts: • State • Event • Reducer • Feedback
truth. It represents a state of your system and is usually a plain Swift type (which doesn't contain any ReactiveSwift primitives). Your state is immutable. The only way to transition from one State to another is to emit an Event. Our viewModel can transition between the following states:
state: Some that come to mind are the following: 1. Enums 2. Structs / Classes 3. Structs / Clases + Context (enum) If you go with enums, while you gain in readability (you always know which state you are in) you have to carry over too much baggage in the form of associated objects.
state: Some that come to mind are the following: 1. Enums 2. Structs / Classes 3. Structs / Clases + Context (enum) If you go with enums, while you gain in readability (you always know which state you are in) you have to carry over too much baggage in the form of associated objects. If you opt for simple structs or classes you can have as much baggage but you have to guess the state you are in.
state: Some that come to mind are the following: 1. Enums 2. Structs / Classes 3. Structs / Clases + Context (enum) If you go with enums, while you gain in readability (you always know which state you are in) you have to carry over too much baggage in the form of associated objects. If you opt for simple structs or classes you can have as much baggage but you have to guess the state you are in. If you opt for a mixed combination, you can have as much baggage as you want while never having to guess your state since you know it from the context. I, obviously opted for #3.
-> State { .init(context: .initial, posts: [], error: nil) } let context: Context let posts: [Post] let error: CoreError? } enum Context: Equatable { case initial case loading case loaded } Post, CoreError and Context all conform to Equatable it is pretty trivial for State to conform to Equatable allowing it to be encoded to data and with minimal effort to be used for state restoration. State also provides more functionality which is pretty trivial and not needed for this presentation.
in your system which can cause a transition to a new State. Our viewModel (will) use the following events: public enum Event { case fetchPosts case fetchPostsSucceeded([Post]) case fetchPostsFailed(CoreError) }
with a signature of (State, Event) -> State. While Event represents an action that might result in a State change, it's actually not what causes the change. An Event is just that, a representation of the intention to transition from one state to another. What actually causes the State to change, the embodiment of the corresponding Event, is a Reducer. A Reducer is the only place where a State can be changed. Let’s see what we want to achieve with our reducer:
is at a given time, Event represents a trigger for state change, and a Reducer is the pure function that changes the state depending on current state and type of event received, there is not as of yet any type to emit events given a particular current state. That's the job of the Feedback. It's essentially a "processing engine", listening to changes in the current State and emitting the corresponding next events to take place. Feedbacks don't directly mutate states. Instead, they only emit events which then cause states to change in reducers.
is at a given time, Event represents a trigger for state change, and a Reducer is the pure function that changes the state depending on current state and type of event received, there is not as of yet any type to emit events given a particular current state. That's the job of the Feedback. It's essentially a "processing engine", listening to changes in the current State and emitting the corresponding next events to take place. Feedbacks don't directly mutate states. Instead, they only emit events which then cause states to change in reducers. Our viewModel uses the following feedbacks: 1. whenExecutingAction 2. whenFetchingPosts
intent to transition to a new state, there is now way to inject events in out FSM. This is where Action comes to play. Action is a simple enum that describes a user action and might be mapped to an event. enum Action { case fetchPosts } For simple apps as this one, it makes no sense to introduce another enum for event injection, but it comes in handy when building complex flows
State is the single source of truth for our ViewModel. Since State will be changing as our ViewModel transitions from state to state, we need some way to observe the changes… both synchronously... and asynchronously.
State is the single source of truth for our ViewModel. Since State will be changing as our ViewModel transitions from state to state, we need some way to observe the changes… both synchronously... and asynchronously. ReactiveSwift’s Property comes to the rescue.
State is the single source of truth for our ViewModel. Since State will be changing as our ViewModel transition from state to state, we need some way to observe the changes… both synchronously... and asynchronously. ReactiveSwift’s Property comes to the rescue. And as it happens, ReactiveFeedback provides an initialiser that can bring everything we discussed together.
State is the single source of truth for our ViewModel. Since State will be changing as our ViewModel transition from state to state, we need some way to observe the changes… both synchronously... and asynchronously. ReactiveSwift’s Property comes to the rescue. And as it happens, ReactiveFeedback provides an initialiser that can bring everything we discussed together. Let’s take a look
that provides Diffing for UITableViews. As stated in the start of this presentation, we will be using the reactive namespace to provide our custom functionality. But before that, let’s take a look at the UITableViewDiffableDataSource’s signature:
Reactive (the struct that actually provides the namespace) to something, but since UITableViewDiffableDataSource is generic we can’t do right away. First, we have to conform UITableViewDiffableDataSource to the following protocol: protocol BindableDataSource: NSObject { associatedtype SectionIdentifier: Hashable associatedtype ItemIdentifier: Hashable func apply(_ snaphsot: [(SectionIdentifier, [ItemIdentifier])], animatingDifferences: Bool, completion: (() -> Void)?) }
do the following in our view controller: fileprivate func bindDataSource() { dataSource.reactive.apply(animatingDifferences: true) <~ viewModel .posts .map(rowsForDataSource) .producer .observe(on: UIScheduler()) } rowsForDataSource is a helper function that maps our posts into rows for our table view.