I am new in relay and saw this on getCollisionKey on treasurehunt tutorial:
getCollisionKey() {
return `check_${this.props.game.id}`;
}
In the docs it states - Implement this method to return a collision key. Relay will send any mutations having the same collision key to the server serially and in-order.
Please help me understand what is getCollisionKey. Would really appreciate.
collisionKey is an identifier to help know when mutations needs to be executed one after the other or when they can be parallelised.
Why we need this is mostly because of network inconsistencies.
Take for example a mutation LikeOrUnlikePost. This mutation likes or unlikes the post depending on if you already like it or not.
Suppose you like the post, then 1s after you decide to unlike.
But the first mutation fails, so it isn't sent to your server, so only one LikeOrUnlikePost mutation is sent.
The result is that you think you unliked the post (you clicked twice), but in fact you only liked it (only one mutation succeed).
This is what collisionKey is for. It tells Relay to queue any mutations which have the same collision key.
In the case above, what would happen is the second mutation would get queued, and would never get executed as the first one fails.
Related
I am aware that using Rx's flatmap or flatmapLatest is preferable to having nested subscriptions. However, I can't find a compelling reason why nested subscription calls "should be avoided at all costs" (RxSwift Github Tips), and want to understand why.
Any insight into the specific issue(s) besides "bad code smell"?
Example (source):
Using nested subscription (bad)
textField.rx.text.subscribe(onNext: { text in
performURLRequest(text).subscribe(onNext: { result in
...
})
.disposed(by: disposeBag)
})
.disposed(by: disposeBag)
Using flatmapLatest (good)
textField.rx.text
.flatMapLatest { text in
// Assuming this doesn't fail and returns result on main scheduler,
// otherwise `catchError` and `observeOn(MainScheduler.instance)` can be used to
// correct this.
return performURLRequest(text)
}
...
.disposed(by: disposeBag) // only one top most disposable
In this particular case, the bad example can yield out of date results - there is nothing that prevents an outdated response to performURLRequest being yielded when the textField updates and triggers a new query.
Of course it depends on your use case, but its often a mistake to display a result (such as a search result) based on an outdated value of textField. Worse, it could happen that an earlier URLRequest runs slowly and returns after a later one, leaving an incorrect result displayed indefinitely.
In contrast flatMapLatest ensures that the pending result stream from the previous value is unsubscribed as soon the textField is updated, which prevents outdated results from being processed.
This concurrency issue is one example of the better coordination and efficiency often achieved by using a single stream.
It also makes subscription management clearer and makes it less likely that you will fail to clean up properly.
I think the first thing to notice is that your two examples don't have the same behavior. The first one will make a network request every time the text field changes without canceling the previous request. In the latter case, when the text changes, the previous request (if any) is cancelled and a new request is started.
If the latter example used flatMap instead of flatMapLatest, they would have been more alike but notice that is the only way the url requests can be combined in the former case. In the latter case you can use flatMapLatest to ensure cancelation of the old request (or flatMapFirst to ignore events when a request is in-flight, or concatMap to store events until the previous request completed.) Using flatMap is much more flexible.
Personally, I do nest subscribes in a few select cases. For example when I'm going to ignore the inner subscribe's events (not even capturing its Disposable) or if setting up an asynchronous looping construct.
We're using Realm (Swift binding currently in version 3.12.0) from the earliest days in our project. In some early versions before 1.0 Realm provided change listeners for Results without actually giving changeSets.
We used this a lot in order to find out if a specific Results list changed.
Later the guys at Realm exchanged this API with changeSet providing methods. We had to switch and are now mistreating this API just in order to find out if anything in a specific List changed (inserts, deletions, modifications).
Together with RxSwift we wrote our own implementation of Results change listening which looks like this:
public var observable: Observable<Base> {
return Observable.create { observer in
let token = self.base.observe { changes in
if case .update = changes {
observer.onNext(self.base)
}
}
observer.onNext(self.base)
return Disposables.create(with: {
observer.onCompleted()
token.invalidate()
})
}
}
When we now want to have consecutive updates on a list we subscribe like so:
someRealm.objects(SomeObject.self).filter(<some filter>).rx.observable
.subscribe(<subscription code that gets called on every update>)
//dispose code missing
We wrote the extension on RealmCollection so that we can subscribe to List type as well.
The concept is equal to RxRealm's approach.
So now in our App we have a lot of filtered lists/results that we are subscribing to.
When data gets more and more we notice significant performance losses when it comes to seeing a change visually after writing something into the DB.
For example:
Let's say we have a Car Realm Object class with some properties and some 1-to-n and some 1-to-1 relationships. One of the properties is a Bool, namely isDriving.
Now we have a lot of cars stored in the DB and bunch of change listeners with different filters listing to changes of the cars collection (collection observers listening for changeSets in order to find out if the list was changed).
If I take one car of some list and set the property of isDriving from false to true (important: we do writes in the background) ideally the change listener fires fast and I have the nearly immediate correct response to my write on the main thread.
Added with edit on 2019-06-19:
Let's make the scenario still a little more real:
Let's change something down the hierarchy, let's say the tires manufacturer's name. Let's say a Car has a List<Tire>, a Tire has a Manufacturer and a Manufacturer has aname.
Now we're still listing toResultscollection changes with some more or less complex filters applied.
Then we're changing the name of aManufacturer` which is connected to one of the tires which are connected to one of the cars which is in that filtered list.
Can this still be fast?
Obviously when the length of results/lists where change listeners are attached to gets longer Realm's internal change listener takes longer to calculate the differences and fires later.
So after a write we see the changes - in worst case - much later.
In our case this is not acceptable. So we are thinking through different scenarios.
One scenario would be to not use .observe on lists/results anymore and switch to Realm.observe which fires every time anything did change in the realm, which is not ideal, but it is fast because the change calculation process is skipped.
My question is: What can I do to solve this whole dilemma and make our app fast again?
The crucial thing is the threading stuff. We're always writing in the background due to our design. So the writes itself should be very fast, but then that stuff needs to synchronize to the other threads where Realms are open.
In my understanding that happens after the change detection for all Results has run through, is that right?
So when I read on another thread, the data is only fresh after the thread sync, which happens after all notifications were sent out. But I am not sure currently if the sync happens before, that would be more awesome, did not test it by now.
When doing a PurchaseOrderQuery in QBXML I am trying to get Quickbooks to only return purchase orders that are not yet processed (i.e. "IsFullyReceived" == false). The response object contains the IsFullyReceived flag, but the query object doesn't seem to have a filter for it??
This means I have to get every single Purchase Order whether or not it's received, then do the filtering logic in my application - which slows down Web Connector transactions.
Any ideas?
Thanks!
You can't.
The response object contains the IsFullyReceived flag, but the query object doesn't seem to have a filter for it??
Correct, there is no filter for it.
You can see this in the docs:
https://developer-static.intuit.com/qbSDK-current/Common/newOSR/index.html
This means I have to get every single Purchase Order whether or not it's received, then do the filtering logic in my application - which slows down Web Connector transactions.
Yep, probably.
Any ideas?
Try querying for only Purchase Orders changed or modified (ModifiedDateRangeFilter) since the last time you synced.
Or, instead of pulling every single PO, keep track of a list of POs that you think may not have been received yet, and then only query for those specific POs based on RefNumber.
Or, watch the ItemReceipt and BillPayment objects, and use that to implement logic about which POs may have been recently filled, since BillPayment andItemReceipt` objects should get created as the PO is fulfilled/received.
I have an API code, which loads a data necessary for my application.
It's as simple as:
- (void) getDataForKey:(NSString*) key onSuccess:(id (^)())completionBlock
I cache data returned from server, so next calls of that functions should not do network request, until there is some data missing for given key, then I need to load it again from server side.
Everything was okey as long as I had one request per screen, but right now I have a case where I need to do that for every cell on one screen.
Problem is my caching doesn't work because before the response comes in from the first one, 5-6 more are created at the same time.
What could be a solution here to not create multiple network request and make other calls waiting for the first one ?
You can try to make a RequestManager class. Use dictionary to cache the requesting request.
If the next request is the same type as first one, don't make a new request but return the first one. If you choose this solution, you need to manager a completionBlock list then you will be able to send result to all requesters.
If the next request is the same type as first one, waiting in another thread until the first one done. Then make a new request, you API will read cache automatically. Your must make sure your codes are thread-safe.
Or you can use operation queues to do this. Some documents:
Apple: Operation Queues
Soheil Azarpour: How To Use NSOperations and NSOperationQueues
May be there will be so many time consuming solutions for this. I have a trick. Create a BOOL in AppDelegate, its default is FALSE. When you receive first response, then set it TRUE. So when you go to other screen and before making request just check value of your BOOL variable in if condition. If its TRUE means response received so go for it otherwise in else don't do anything.
When I implement the getLoggedInUserOnSuccess:onFailure method (or the loginWithUsername: password: onSuccess:^(NSDictionary *results)...method in xcode, the results array does not become available until after all of my code has finished running. (If I NSLog the results, they will show up correctly.)
There is one other question that mentions this in Stackoverflow:
How to get the sm_owner User object using StackMob as backend
However, the Stackmob Evangelist in the answer here does not suggest that there is any requirement for a completion block or something of this nature. (And in fact, in his own code, it appears to work without such a block or any sort of "waiting.") This was my first hunch as to what might be going wrong.
(Without posting a ton of code, I am trying to use this function to get the sm_owner which then serves as the predicate in the FetchedResultsController's getter, to ensure the user only sees their own creations and not those of other users, when in one view; in another fetch, they might be able to see the creations of users they follow.)
Has anyone else tried to use one of these methods with a results dictionary returned to write the predicate on a FetchedResultsController or similar and been able to make it work?
None of the Stackmob tutorials appear to limit data returned from a database based on its creator as far as I can tell.
If you set your schema's read permissions to "Allow to sm_owner", then you don't need to place a predicate on the fetch. Doing a generic fetch will return only those objects owned by the current logged in user.