Currently I'm having a Observable created using scan to update underlying model using a PublishSubject like this:
class ViewModel {
private enum Action {
case updateName(String)
}
private let product: Observable<Product>
private let actions = PublishSubject<Action>()
init(initialProduct: Product) {
product = actions
.scan(initialProduct, accumulator: { (oldProduct, action) -> Product in
var newProduct = oldProduct
switch action {
case .updateName(let name):
newProduct.name = name
}
return newProduct
})
.startWith(initialProduct)
.share()
}
func updateProductName(_ name: String) {
actions.onNext(.updateName(name))
}
private func getProductDetail() {
/// This will call a network request
}
}
Every "local" actions like update product's name, prices... is done by using method like updateProductName(_ name: String) above. But what if I want to have a network request that also update the product, and can be called every time I want, for example after a button tap, or after calling updateProductName?
// UPDATE: After read iWheelBuy's comment and Daniel's answer, I ended up using 2 more actions
class ViewModel {
private enum Action {
case getDetail
case updateProduct(Product)
}
///....
init(initialProduct: Product) {
product = actions
.scan(initialProduct, accumulator: { (oldProduct, action) -> Product in
var newProduct = oldProduct
switch action {
case .updateName(let name):
newProduct.name = name
case .getDetail:
self.getProductDetail()
case .updateProduct(let p):
return p
}
return newProduct
})
.startWith(initialProduct)
.share()
}
func getProductDetail() {
actions.onNext(.getDetail)
}
private func getProductDetail(id: Int) {
ProductService.getProductDetail(id) { product in
self.actions.onNext(.updateProduct(product))
}
}
}
But I feel that, I trigger side effect (call network request) inside scan, without updating the model, is that something wrong?
Also how can I use a "rx" network request?
// What if I want to use this method instead of the one above,
// without subscribe inside viewmodel?
private func rxGetProductDetail(id: Int) -> Observable<Product> {
return ProductService.rxGetProductDetail(id: Int)
}
I'm not sure why #iWheelBuy didn't make a real answer because their comment is the correct answer. Given the hybrid approach to Rx in your question, I expect something like the below will accommodate your style:
class ViewModel {
private enum Action {
case updateName(String)
case updateProduct(Product)
}
private let product: Observable<Product>
private let actions = PublishSubject<Action>()
private var disposable: Disposable?
init(initialProduct: Product) {
product = actions
.scan(initialProduct, accumulator: { (oldProduct, action) -> Product in
var newProduct = oldProduct
switch action {
case .updateName(let name):
newProduct.name = name
case .updateProduct(let product):
newProduct = product
}
return newProduct
})
.startWith(initialProduct)
.share()
// without a subscribe, none of this matters. I assume you just didn't show all your code.
}
deinit {
disposable?.dispose()
}
func updateProductName(_ name: String) {
actions.onNext(.updateName(name))
}
private func getProductDetail() {
let request = URLRequest(url: URL(string: "https://foo.com")!)
disposable?.dispose()
disposable = URLSession.shared.rx.data(request: request)
.map { try JSONDecoder().decode(Product.self, from: $0) }
.map { Action.updateProduct($0) }
.subscribe(
onNext: { [actions] in actions.onNext($0) },
onError: { error in /* handle error */ }
)
}
}
The style above is still pretty imperative but if you don't want your use of Rx to leak out of the view model it's okay.
If you want to see a "full Rx" setup, you might find my sample repo interesting: https://github.com/danielt1263/RxEarthquake
UPDATE
But I feel that, I trigger side effect (call network request) inside scan, without updating the model, is that something wrong?
The scan function should be pure with no side effects. Calling a network request inside it's closure is inappropriate.
Related
I want to merge ReactorKit with RxFlow. Is there an elegant idea how to bind any action or mutation to steps emitter likes PublishRelay inside of Reactor
My sample
import ReactorKit
import RxFlow
class ViewModel: Reactor, Stepper {
let steps: PublishRelay<ItemsStep>
enum Action {
case itemSelected(Item.ID)
}
enum Mutation {
case itemSelectionResult(Item.Details) // ??
}
func mutate(action: Action) -> Observable<Mutation> {
switch action {
case .itemSelected(let id):
return ItemsService.fetch(by: id)
// ... ??? bind(to: steps)
// ... ??? map(Mutation.itemSelectionResult)
}
}
}
I would like to avoid observable do methods with self capturing like
func mutate(action: Action) -> Observable<Mutation> {
switch action {
case .itemSelected(let id):
return ItemsService.fetch(by: id)
.do { [weak self] info in
self?.steps.accept(ItemsStep.details(info))
}
.map(Mutation.itemSelectionResult)
}
}
Let's say there are three components and their respective dynamic dependencies:
struct Component1 {
let dependency1: Dependency1
func convertOwnDependenciesToDependency2() -> Dependency2
}
struct Component2 {
let dependency2: Dependency2
let dependency3: Dependency3
func convertOwnDependenciesToDependency4() -> Dependency4
}
struct Component3 {
let dependency2: Dependency2
let dependency4: Dependency4
func convertOwnDependenciesToDependency5() -> Dependency5
}
Each of those components can generate results which can then be used as dependencies of other components. I want to type-safely inject the generated dependencies into the components which rely on them.
I have several approaches which I already worked out but I feel like I am missing something obvious which would make this whole task way easier.
The naive approach:
let component1 = Component1(dependency1: Dependency1())
let dependency2 = component1.convertOwnDependenciesToDependency2()
let component2 = Component2(dependency2: dependency2, dependency3: Dependency3())
let dependency4 = component2.convertOwnDependenciesToDependency4()
let component3 = Component3(dependency2: dependency2, dependency4: dependency4)
let result = component3.convertOwnDependenciesToDependency5()
Now I know that you could just imperatively call each of the functions and simply use the constructor of each component to inject their dependencies. However this approach does not scale very well. In a real scenario there would be up to ten of those components and a lot of call sites where this approach would be used. Therefore it would be very cumbersome to update each of the call sites if for instance Component3 would require another dependency.
The "SwiftUI" approach:
protocol EnvironmentKey {
associatedtype Value
}
struct EnvironmentValues {
private var storage: [ObjectIdentifier: Any] = [:]
subscript<Key>(_ type: Key.Type) -> Key.Value where Key: EnvironmentKey {
get { return storage[ObjectIdentifier(type)] as! Key.Value }
set { storage[ObjectIdentifier(type)] = newValue as Any }
}
}
struct Component1 {
func convertOwnDependenciesToDependency2(values: EnvironmentValues) {
let dependency1 = values[Dependency1Key.self]
// do some stuff with dependency1
values[Dependency2Key.self] = newDependency
}
}
struct Component2 {
func convertOwnDependenciesToDependency4(values: EnvironmentValues) {
let dependency2 = values[Dependency2Key.self]
let dependency3 = values[Dependency3Key.self]
// do some stuff with dependency2 and dependency3
values[Dependency4Key.self] = newDependency
}
}
struct Component3 {
func convertOwnDependenciesToDependency5(values: EnvironmentValues) {
let dependency2 = values[Dependency2Key.self]
let dependency4 = values[Dependency4Key.self]
// do some stuff with dependency2 and dependency4
values[Dependency5Key.self] = newDependency
}
}
But what I dislike with this approach is that you first of all have no type-safety and have to either optionally unwrap the dependency and give back an optional dependency which feels odd since what should a component do if the dependency is nil? Or force unwrap the dependencies like I did. But then the next point would be that there is no guarantee whatsoever that Dependency3 is already in the environment at the call site of convertOwnDependenciesToDependency4. Therefore this approach somehow weakens the contract between the components and could make up for unnecessary bugs.
Now I know SwiftUI has a defaultValue in its EnvironmentKey protocol but in my scenario this does not make sense since for instance Dependency4 has no way to instantiate itself without data required from Dependency2 or Depedency3 and therefore no default value.
The event bus approach
enum Event {
case dependency1(Dependency1)
case dependency2(Dependency2)
case dependency3(Dependency3)
case dependency4(Dependency4)
case dependency5(Dependency5)
}
protocol EventHandler {
func handleEvent(event: Event)
}
struct EventBus {
func subscribe(handler: EventHandler)
func send(event: Event)
}
struct Component1: EventHandler {
let bus: EventBus
let dependency1: Dependency1?
init(bus: EventBus) {
self.bus = bus
self.bus.subscribe(handler: self)
}
func handleEvent(event: Event) {
switch event {
case let .dependency1(dependency1): self.dependency1 = dependency1
}
if hasAllReceivedAllDependencies { generateDependency2() }
}
func generateDependency2() {
bus.send(newDependency)
}
}
struct Component2: EventHandler {
let dependency2: Dependency2?
let dependency3: Dependency3?
init(bus: EventBus) {
self.bus = bus
self.bus.subscribe(handler: self)
}
func handleEvent(event: Event) {
switch event {
case let .dependency2(dependency2): self.dependency2 = dependency2
case let .dependency3(dependency3): self.dependency3 = dependency3
}
if hasAllReceivedAllDependencies { generateDependency4() }
}
func generateDependency4() {
bus.send(newDependency)
}
}
struct Component3: EventHandler {
let dependency2: Dependency2?
let dependency4: Dependency4?
init(bus: EventBus) {
self.bus = bus
self.bus.subscribe(handler: self)
}
func handleEvent(event: Event) {
switch event {
case let .dependency2(dependency2): self.dependency2 = dependency2
case let .dependency4(dependency4): self.dependency4 = dependency4
}
if hasAllReceivedAllDependencies { generateDependency5() }
}
func generateDependency5() {
bus.send(newDependency)
}
}
I think in terms of type-safety and "dynamism" this approach would be a good fit. However to check if all dependencies were received already to start up the internal processes feels like a hack somehow. It feels like I am misusing this pattern in some form. Furthermore I think this approach may be able to "deadlock" if some dependency event was not sent and is therefore hard to debug where it got stuck. And again I would have to force unwrap the optionals in generateDependencyX but since this function would only get called if all optionals have a real value it seems safe to me.
I also took a look at some other design patterns (like chain-of-responsibility) but I couldn't really figure out how to model this pattern to my use-case.
My dream would be to somehow model a given design pattern as a result builder in the end so it would look something like:
FinalComponent {
Component1()
Component2()
Component3()
}
And in my opinion result builders would be possible with the "SwiftUI" and the event bus approach but they have the already described drawbacks. Again maybe I am missing an obvious design pattern which is already tailored to this situation or I am just modeling the problem in a wrong way. Maybe someone has a suggestion.
I have an object and its properties as following:
class Section {
var cards: [MemberCard]
init(card: [MemberCard]) {
}
}
class MemberCard {
var name: String
var address: String?
init(name: String) {
self.name = name
}
}
I'm subscribing to a RxStream of type Observable<[Section]>. Before I subscribe I would to want flat map this function.
where the flat map would perform the following actions:
let sectionsStream : Observable<[Section]> = Observable.just([sections])
sectionsStream
.flatMap { [weak self] (sections) -> Observable<[Section]> in
for section in sections {
for card in section.cards {
}
}
}.subscribe(onNext: { [weak self] (sections) in
self?.updateUI(memberSections: sections)
}).disposed(by: disposeBag)
func getAddressFromCache(card: MemberCard) -> Observable<MemberCard> {
return Cache(id: card.name).flatMap ({ (card) -> Observable<MemberCard> in
asyncCall{
return Observable.just(card)
}
}
}
How would the flatmap look like when it comes to converting Observable<[Section]> to array of [Observable<MemberCard>] and back to Observable<[Section]>?
Technically, like that -
let o1: Observable<MemberCard> = ...
let o2: Observable<Section> = omc.toList().map { Section($0) }
let o2: Observable<[Section]> = Observable.concat(o2 /* and all others */).toList()
But I do not think it is an optimal solution, at least because there is no error handling for the case when one or more cards cannot be retrieved. I would rather build something around aggregation with .scan() operator as in https://github.com/maxvol/RaspSwift
Here you go:
extension ObservableType where E == [Section] {
func addressedCards() -> Observable<[Section]> {
return flatMap {
Observable.combineLatest($0.map { getAddresses($0.cards) })
}
.map {
$0.map { Section(cards: $0) }
}
}
}
func getAddresses(_ cards: [MemberCard]) -> Observable<[MemberCard]> {
return Observable.combineLatest(cards
.map {
getAddressFromCache(card: $0)
.catchErrorJustReturn($0)
})
}
If one of the caches emits an error, the above will return the MemberCard unchanged.
I have a couple of other tips as well.
In keeping with the functional nature of Rx, your Section and MemberCard types should either be structs or (classes with lets instead of vars).
Don't use String? unless you have a compelling reason why an empty string ("") is different than a missing string (nil). There's no reason why you should have to check existence and isEmpty every time you want to see if the address has been filled in. (The same goes for arrays and Dictionaries.)
For this code, proper use of combineLatest is the key. It can turn an [Observable<T>] into an Observable<[T]>. Learn other interesting ways of combining Observables here: https://medium.com/#danielt1263/recipes-for-combining-observables-in-rxswift-ec4f8157265f
I need a single function to resolve different dependencies in a class.
But there is a compilation error appears.
Is it possible to create that generic function or there are some compiler constraints in Swift?
import Foundation
protocol Client: class {
var description: String { get }
}
final class ImportantPerson : Client {
var description: String {
return "Important person"
}
}
protocol Order: class {
var description: String { get }
}
final class LastOrder : Order {
var description: String {
return "Last order"
}
}
final class A {
fileprivate func resolveDependency<T>() -> T {
return resolve() as T
}
private func resolve() -> Client {
return ImportantPerson()
}
private func resolve() -> Order {
return LastOrder()
}
}
let a = A()
let client: Client = a.resolveDependency()
let order: Order = a.resolveDependency()
print("Client: \(client.description)")
print("Order: \(order.description)")
EDIT: This question is not about if Swift allows to create two functions that differs only by return type. I know it's possible. I think there are some artificial constraints in the compiler but not in the fundamental logic that should allow to infer needed type from a context.
Let's put yourself into the compiler's shoes. Imagine that this was not causing an error and you had one signature with different outputs.
Whenever you call resolveDependency<T>() -> T, the compiler will return you a type T which is an instance conforming to a protocol in your case.
In your code you call this method with different instances conforming to the same protocol. At that stage the compiler has no idea about this. All it knows is that you have passed an instance of T and it needs to give you a result in shape of T
There is no problem until this point. As soon as you execute
return resolve() as! T
The compiler will be confused. I have a T but I don't know which resolve() I will call... All I know is that I have a T. How would I know if this is an Order or a Client ?
In order to prevent such confusions we have compiler-time errors. At least this is the case for Swift. (I don't know how this works in other languages)
You need to define different methods with different signatures and cast your type accordingly to get a similar result
fileprivate func resolveDependency<T>() -> T {
// check if this is an Order
resolveForOrder()
// check if this is a Client
resolveForClient()
}
private func resolveForOrder() -> Order {
return LastOrder()
}
private func resolveForClient() -> Client {
return ImportantPerson()
}
This is like trying to fix a space shuttle engine with a car mechanic. Yes, they both have an engine, they both run on fuel but the mechanic only knows how to fix your car's engine he is not a rocket scientist(!)
This code works fine:
import Foundation
protocol Client: class {
var description: String { get }
}
final class ImportantPerson : Client {
var description: String {
return "Important person"
}
}
protocol Order: class {
var description: String { get }
}
final class LastOrder : Order {
var description: String {
return "Last order"
}
}
final class A {
fileprivate func resolveDependency<T>() -> T {
if T.self == Client.self {
return resolve() as Client as! T
} else {
return resolve() as Order as! T
}
}
private func resolve() -> Client {
return ImportantPerson()
}
private func resolve() -> Order {
return LastOrder()
}
}
let a = A()
let client: Client = a.resolveDependency()
let order: Order = a.resolveDependency()
print("Client: \(client.description)")
print("Order: \(order.description)")
But I believe that compiler should resolve the if else clause himself, it's not so hard as I suppose.
Also there is some bug in the compiler when it tries to match types like that:
switch T.self {
case is Client:
return resolve() as Client as! T
default:
return resolve() as Order as! T
}
I'm working on a local game which relies on turn order.
Rules;
There are a number of phases in the game (ie: Buy, Sell)
During each phase, a player takes a single turn
Each phase is not considered complete until every player (in turn order) has completed their turn.
I'm not sure how to manage this data. There are a number of things to track.
The phase we are in
The current player on turn
When all players have completed their turns
When the end of the turn order has been reached so we can move to next phase.
Resetting all turn completions when all phases are complete
I'm thinking that a subscription model is the best approach to this, but I'm not used to such a pattern.
Currently I'm using a similar system to a to-do where the phase itself can be marked complete or incomplete.
This is the way I'm currently handling turn orders and phases in Swift playground.
// Turn order management
class TurnOrderManager: NSObject
{
static var instance = TurnOrderManager()
var turnOrder: [Player] = [Player]()
private var turnOrderIndex = 0
var current: Player {
return turnOrder[turnOrderIndex]
}
func next() {
if (self.turnOrderIndex < (self.turnOrder.count-1)) {
turnOrderIndex += 1
}
else {
print("Reached end of line")
}
}
}
class Player: NSObject {
var name: String = ""
override var description: String {
return self.name
}
init(name: String) {
super.init()
self.name = name
}
}
let p1:Player = Player.init(name: "Bob")
let p2:Player = Player.init(name: "Alex")
TurnOrderManager.instance.turnOrder = [p1,p2]
print (TurnOrderManager.instance.current)
TurnOrderManager.instance.next()
print (TurnOrderManager.instance.current)
TurnOrderManager.instance.next()
print (TurnOrderManager.instance.current)
// ---------------------------------
// Phase management
enum PhaseType: Int {
case buying = 1
case selling
}
struct Phase {
var id: PhaseType
var title: String
var completed: Bool = false {
didSet {
// Notify subscribers of completion
guard completed else { return }
handlers.forEach { $0(self) }
}
}
var description:String {
return "Phase: \(self.title), completed: \(completed)"
}
// Task queue
var handlers = [(Phase) -> Void]()
init(id: PhaseType, title: String, initialSubscription: #escaping (Phase) -> Void =
{_ in})
{
self.id = id
self.title = title
subscribe(completion: initialSubscription)
}
mutating func subscribe(completion: #escaping (Phase) -> Void) {
handlers.append(completion)
}
}
class MyParentController {
lazy var phase1: Phase = {
return Phase(id: .buying, title: "Phase #1") {
print("Do something with phase: \($0.title)")
}
}()
}
let controller = MyParentController()
controller.phase1.completed = true
Question:
I'm wanting to notify:
Turn is complete
All turns are complete (so that it can move to next phase)
How do I make my TurnOrderManager alert the PhaseManager that the current turn is complete.
How do I make my PhaseManager know that when all turns are complete to move to the next phase.
I apologize for the verboseness of my query.
Many thanks
You're going to want to define a delegate relationship PhaseManager and your TurnOrderManager.
Here is the Apple docs on protocols.
Here is a great article on delegation.
First you'll need to define a protocol. Something like this:
protocol TurnManagerDelegate {
func complete(turn: objectType)
func allTurnsComplete()
}
Next you'll have to conform your PhaseManager to the protocol:
class PhaseManager: TurnManagerDelegate {
...
func complete(turn: objectType) {
// implement
}
func allTurnsComplete() {
// implement
}
...
}
Last you'll have to add a property on your TurnOrderManager with the delegate:
class TurnOrderManager {
...
var delegate: TurnManagerDelegate
...
}
and call the functions whenever needed in your TurnOrderManager:
...
delegate?.allTurnsComplete() //example
...
You'll also have to set your PhaseManager as the delegate before your TurnOrderManager would try to call any of the delegate methods.