Combine prefetching and caching results - swift

I'm looking for a way to prefetch data and cache it without any subscriptions made towards that subscriber or when first subscription is made.
For example :
products = getProducts.shareReply(scope : forever)
And when new subscribers subscribes it should receive the last data. If there is no subscribers it's shouldn't restart this publisher on next subscription instead it should return last cached data.
I'm already using shareReply operator with multicast, custom ReplaySubject and autoconnect but if there is no subscriptions towards the publisher next time something subscribes it will restart the publisher and new data will be fetched. And I don't want that.
This is code for shareReply operator :
extension Publisher {
func shareReplay(_ bufferSize: Int = 1) -> AnyPublisher<Output, Failure> {
return multicast(subject: ShareReplySubject(bufferSize))
.autoconnect()
.eraseToAnyPublisher()
}
}

I'm aware of it but I would like to turn AnyPublisher to something like Behaviour(CurrentValue)Subject I mean I want to turn anyPublisher to "hot publisher" and so it emits data regardless of if there is a subscriber or not.
You can do this by applying the multicast operator and calling connect on the result:
let (sharedProducts, sharedProductsTicket): (AnyPublisher<[Product], Never>, Cancellable) = {
let pub = getProducts()
.multicast(subject: CurrentValueSubject([]))
return (pub.eraseToAnyPublisher(), pub.connect())
}()
Now sharedProducts has a live subscription to getProducts() and stores the latest value published by getProducts(). Anyone who subscribes to sharedProducts immediately receives the latest value, and receives new values as they arrive.
The pub.connect() call returns a Cancellable that, when destroyed, cancels the inner subscription connecting getProducts() to the CurrentValueSubject. So you have to store that ticket somewhere. In my example, I store it in sharedProductsTicket.

Related

Get specific .childrenCount from Firebase

In my new app (Project Control, iOS App Store ;)) I want users to take part of development decisions. For this I have added a path in my Firebase database called "claps". I would like to enter the number of the following in my TableView for the different concepts. I have tried the following
self.posts.append(Post(title: post_title, des: post_description, info: "\(post_date) - \(post_user) - \(post_claps) 👏", claps: Int(post_claps)))
for var item in self.posts {
g.ref.child("concepts").child(item.title).queryOrdered(byChild: "claps").observe(.childAdded) { (snapshotClaps: DataSnapshot!) in
item.claps = Int(snapshotClaps.childrenCount)
}
}
DispatchQueue.main.async() {
self.tableView.reloadData()
}
However, it does not yet represent the right one, but is one before it. I don't know how to make the reference more specific to really get only what's under claps.
This ist my Database:
Currently my output is 5 but it should be 4. You see its observing one "layer" to early. Help will be appreciated. Improvements too :)
UPDATE:
Through testing I could reveal that the problem is in the reference. The Int five is coming from the 5 Childs of "top-layer" "Journal". My problem is that I cant get any deeper in the structure because I don't have a specific String for .child()
Since you're observing the .childAdded event, your closure gets called for each matching child node. If you want to count the number of matching child nodes, you'll want to observe the .value event, which ensures your closure gets called for all matching nodes at once.
Something like:
g.ref.child("concepts").child(item.title).observe(.value) { (snapshotClaps: DataSnapshot!) in
item.claps = Int(snapshotClaps.childrenCount)
}
Note that I also removed the orderBy clause, since that has no useful meaning if all you use is the count.
create an Array and allow the firebase to populate it. or do something like
g.ref.child("concepts").child(item.title).observe(.value) { (snapshotClaps: DataSnapshot!) in
item.claps = Int(snapshotClaps.childrenCount)
}
observing value makes sure your closure gets its matching nodes.
There's a couple of great solutions but the issue in reading a node by .value is it reads in everything in that node.
While that would be fine for nodes that have a limited amount of data, it would overwhelm the device when the node contains a lot of data.
So another option is to leverage that Firebase executes all .childAdded events before .value events. That way, we can use a .value as a trigger that all nodes have been read.
Here's a function that uses .childAdded to iterate and count all of the users in the users node. Also, there's a .value observer that reads in just the last node, removes the .childAdded observer and passes the count back to the calling function via a completion handler. Remember that even though we are attaching both observers, the .childAdded events will all fire before the .value event.
func countUsers( completion: #escaping(Int) -> Void) {
var count = 0
let usersRef = self.ref.child("users")
usersRef.observe(.childAdded, with: { snapshot in
count+=1
})
let query = usersRef.queryOrderedByKey().queryLimited(toLast: 1)
query.observeSingleEvent(of: .value, with: { snapshot in
usersRef.removeAllObservers()
completion(count)
})
}
to call the function, here's the code
func getUserCount() {
self.countUsers(completion: { userCount in
print("number of users: \(userCount)")
})
}

Mutating of RxSwift Driver value

In my application I have an array of notifications. Notification can be read and unread.
When user clicks on the unread notification I need to change the model and reload data in my table view.
In my ViewModel I have output stream:
let notifications: Driver<[Notification]>
And aslo I have an input stream with notification click:
let touchSingleNotificationIntent = PublishSubject<Notification>()
When I do something like this I get the error that it's let constant and I cannot mutate it.
touchSingleNotificationIntent
.filter { !$0.isRead }
.do(onNext: { notification in
notification.isRead = true // I need to change state of the model immediately after user iteration
})
.map { $0.notificationID }
.flatMap(markNotificationAsRead) // http request which doesn't reply with current notification model status
.subscribe()
.disposed(by: bag)
Do you have any ideas how to make it mutable? Thanks.
Streams aren't Mutable at all (this is the same for Observable, Driver, and any other traits). They are "Read only", you read values off the stream over time.
In general, the conception Observables has a "value" is a bit wrong since Observables represent a value over time, and not just a single value.
What you would want to do is "take into account" your PublishSubject when building out your driver.
Something like this would work:
notifications = Observable
.combineLatest(touchedNotification, readNotification, otherEvent) { ($0, $1, $2) }
.map { ... map the three values into whatever makes sense for you }
.asDriver(onErrorJustReturn: ... fallback value ... }
Again, the most important fact to remember - You do not actually mutate streams, you only combine them, transform them, etc, to create a new stream that suits your needs.
Hope this helps you!
Parameters of onNext are let by default. You can define a new one with var, i.e. 'var newNotification = notification' and then return it after modifying.

Creating a user-controllable RxSwift Observer

I'm trying to implement user-driven refreshing in my Rx based networking code, and my current design is as follows:
Create a sink that has Void values passed into it every time the user initiates a refresh action
flatMap the latest .Next event on that sink's Observable into a new network call
Transform the network response into a new view model and pass that back into the view controller
The part I'm getting hung up on is how to create a sink for those events to go down. My current code is as follows:
func contactListModel() -> Observable<ContactListViewModel<Contact>> {
// Create a sink for refresh events
var refreshSink: AnyObserver<Void> = AnyObserver { event in }
let refreshObservable = Observable<Void>.create { observer in
refreshSink = observer
return NopDisposable.instance
}
// Define action handlers
let searchClosure = { (query: String?) in
self.contactsSearchTerm.value = query
}
let refreshClosure = refreshSink.onNext
// TODO: [RP] Make contact list view controller handle a nil view model to remove the need for this code
let initialViewModel = ContactListViewModel<Contact>(contacts: [], searchClosure: searchClosure, refreshClosure: refreshClosure)
// Perform an initial refresh
defer {
refreshSink.onNext()
}
// Set up subscription to push a new view model each refresh
return refreshObservable
.flatMapLatest {
return self.networking.request(.ListContacts)
}
.mapToObject(ListContactsResponse)
.map { response in
return ContactListViewModel(contacts: response.contacts, searchClosure: searchClosure, refreshClosure: refreshClosure)
}
.startWith(initialViewModel)
}
Now it's obvious why my code to create an event sink doesn't work here. The block being passed into refreshObservable's create method is only called once the observer is subscribed to, so the refreshSink won't be reassigned until then. Furthermore, if this observable is subscribed to more than once, the refreshSink variable will be reassigned.
So my question is this: how do I create an Observable that I can manually push events down? Or alternatively, is there a better design I could be using here?
I know ReactiveCocoa has the pipe static method on Signal that will do something like what I'm looking for, but I've found no equivalent in the Rx API.

RXJS : Idiomatic way to create an observable stream from a paged interface

I have paged interface. Given a starting point a request will produce a list of results and a continuation indicator.
I've created an observable that is built by constructing and flat mapping an observable that reads the page. The result of this observable contains both the data for the page and a value to continue with. I pluck the data and flat map it to the subscriber. Producing a stream of values.
To handle the paging I've created a subject for the next page values. It's seeded with an initial value then each time I receive a response with a valid next page I push to the pages subject and trigger another read until such time as there is no more to read.
Is there a more idiomatic way of doing this?
function records(start = 'LATEST', limit = 1000) {
let pages = new rx.Subject();
this.connect(start)
.subscribe(page => pages.onNext(page));
let records = pages
.flatMap(page => {
return this.read(page, limit)
.doOnNext(result => {
let next = result.next;
if (next === undefined) {
pages.onCompleted();
} else {
pages.onNext(next);
}
});
})
.pluck('data')
.flatMap(data => data);
return records;
}
That's a reasonable way to do it. It has a couple of potential flaws in it (that may or may not impact you depending upon your use case):
You provide no way to observe any errors that occur in this.connect(start)
Your observable is effectively hot. If the caller does not immediately subscribe to the observable (perhaps they store it and subscribe later), then they'll miss the completion of this.connect(start) and the observable will appear to never produce anything.
You provide no way to unsubscribe from the initial connect call if the caller changes its mind and unsubscribes early. Not a real big deal, but usually when one constructs an observable, one should try to chain the disposables together so it call cleans up properly if the caller unsubscribes.
Here's a modified version:
It passes errors from this.connect to the observer.
It uses Observable.create to create a cold observable that only starts is business when the caller actually subscribes so there is no chance of missing the initial page value and stalling the stream.
It combines the this.connect subscription disposable with the overall subscription disposable
Code:
function records(start = 'LATEST', limit = 1000) {
return Rx.Observable.create(observer => {
let pages = new Rx.Subject();
let connectSub = new Rx.SingleAssignmentDisposable();
let resultsSub = new Rx.SingleAssignmentDisposable();
let sub = new Rx.CompositeDisposable(connectSub, resultsSub);
// Make sure we subscribe to pages before we issue this.connect()
// just in case this.connect() finishes synchronously (possible if it caches values or something?)
let results = pages
.flatMap(page => this.read(page, limit))
.doOnNext(r => this.next !== undefined ? pages.onNext(this.next) : pages.onCompleted())
.flatMap(r => r.data);
resultsSub.setDisposable(results.subscribe(observer));
// now query the first page
connectSub.setDisposable(this.connect(start)
.subscribe(p => pages.onNext(p), e => observer.onError(e)));
return sub;
});
}
Note: I've not used the ES6 syntax before, so hopefully I didn't mess anything up here.

rx reactive extension: how to have each subscriber get a different value (the next one) from an observable?

Using reactive extension, it is easy to subscribe 2 times to the same observable.
When a new value is available in the observable, both subscribers are called with this same value.
Is there a way to have each subscriber get a different value (the next one) from this observable ?
Ex of what i'm after:
source sequence: [1,2,3,4,5,...] (infinite)
The source is constantly adding new items at an unknown rate.
I'm trying to execute a lenghty async action for each item using N subscribers.
1st subscriber: 1,2,4,...
2nd subscriber: 3,5,...
...
or
1st subscriber: 1,3,...
2nd subscriber: 2,4,5,...
...
or
1st subscriber: 1,3,5,...
2nd subscriber: 2,4,6,...
I would agree with Asti.
You could use Rx to populate a Queue (Blocking Collection) and then have competing consumers read from the queue. This way if one process was for some reason faster it could pick up the next item potentially before the other consumer if it was still busy.
However, if you want to do it, against good advice :), then you could just use the Select operator that will provide you with the index of each element. You can then pass that down to your subscribers and they can fiter on a modulus. (Yuck! Leaky abstractions, magic numbers, potentially blocking, potentiall side effects to the source sequence etc)
var source = Obserservable.Interval(1.Seconds())
.Select((i,element)=>{new Index=i, Element=element});
var subscription1 = source.Where(x=>x.Index%2==0).Subscribe(x=>DoWithThing1(x.Element));
var subscription2 = source.Where(x=>x.Index%2==1).Subscribe(x=>DoWithThing2(x.Element));
Also remember that the work done on the OnNext handler if it is blocking will still block the scheduler that it is on. This could affect the speed of your source/producer. Another reason why Asti's answer is a better option.
Ask if that is not clear :-)
How about:
IObservable<TRet> SomeLengthyOperation(T input)
{
return Observable.Defer(() => Observable.Start(() => {
return someCalculatedValueThatTookALongTime;
}, Scheduler.TaskPoolScheduler));
}
someObservableSource
.SelectMany(x => SomeLengthyOperation(input))
.Subscribe(x => Console.WriteLine("The result was {0}", x);
You can even limit the number of concurrent operations:
someObservableSource
.Select(x => SomeLengthyOperation(input))
.Merge(4 /* at a time */)
.Subscribe(x => Console.WriteLine("The result was {0}", x);
It's important for the Merge(4) to work, that the Observable returned by SomeLengthyOperation be a Cold Observable, which is what the Defer does here - it makes the Observable.Start not happen until someone Subscribes.