Syncing UITableview with subscription data - swift

I have a Tableview with somewhat complex operations where
sections are inserted and updated in realtime though GraphQl subscriptions.
At the moment I'm having some problems with race conditions.
When i receive new data through the subscription i parse it into my Model and update the tableview with insertions and updating the content and size on some existing cells.
The problem arrises when i get data faster then i can finish the previous update in the table resulting in an "invalid number of sections" crash.
I believe a solution is to serial/wait the sequence subscription -> model -> tableview.
Ive tried to get this to work with various concurrency methods such as, semaphore, sync, barrier, dispatch group. but have not been able to successfully figure it out.
If i try to simplify, the sequence of events transpires something like this.
//Model
subscription { data in
//should not start parsing new data until previous data has been drawn in table to avoid missmatch in model and table
parse(data)
}
func parse(data) {
//do stuff like update datamodel
figureOutWhatToUpdateInTable(data) { (insertSet, reloadSet) in
delegate.updateTableView(insertSet, reloadSet)
}
//do stuff
}
//VC
func updateTableView(insertSet, reloadSet) {
tableView.beginUpdates()
CATransaction.begin()
//once a new section is inserted we need to update content of some sections
CATransaction.setCompletionBlock {
reloadSet.forEach { (index: Int) in
let section = tableView.headerView(forSection: index)
section.updateData(data[index]) {
// call begin/end to make tableview get height
tableView.beginUpdates()
tableView.endUpdates()
// now im ready to parse new data into my model
}
})
}
tableView.insertSections(insetSet, with: .top)
CATransaction.commit()
tableView.endUpdates()
}
basically i need to wait for section.updateData to finish before parse(data) processes any new data from the subscription

You can use at this case DispatchGroup
let group = DispatchGroup()
group.enter()
// do some stuff (1)
group.leave()
group.notify(queue: .main) {
// do stuff after 1
}
After call group.leave() group avtomaticaly execute STUFF at group.notify Block

Related

Swift - Can my code switch threads when calling a function

In the code below, I have reduced many lines into, what I hope are, the essentials. I'm getting this error:
UI API called on a background thread
My question is, when I call a method from inside DispatchQueue.main.async, does that method get executed on the main thread as well? I'm doubly confused because, 1) I thought I was on the main thread, and second, I didn't think I was doing anything UI related.
//Downloading data on a background thread above this line
DispatchQueue.main.async {
//Go through all the recently downloaded customers
for (index, customer) in customers.enumerated() {
//for each, add a new customer to core data
self.createNewCustomer(customer: customer)
}
}
func createNewCustomer(customer: Customer) {
let newCustomer = Customers(context: self.context)
...
//Convert time to seconds using a method in a different view controller.
let seconds = CustomerViewController().secondsFromDate(date: date)
//Save to core data and repeat
appDelegate.saveContext()
}

How to call every struct method inside write transaction

I created struct Repository for manipulating with objects of Realm database (changing some properties, adding new objects, deleting, etc.). When I want to write to the database, I have to do it inside do-try-catch block, so I created a method with completion which I call every time I need to write something to the database
private func action(_ completion: () -> Void) {
do {
try realm.write {
completion()
}
} catch {
print(error)
}
}
then I call methods for manipulating with objects like this:
func createObject(_ object: MyObject) {
action {
realm.add(object)
}
}
func deleteObject(_ object: MyObject) {
action {
realm.delete(object)
}
}
func setTitleForObject(_ object: MyObject, title: String) {
action {
object.title = title
}
}
...
My question is, is there any way how I can call every method inside this Repository struct inside write transaction in do-try-catch block by default instead of calling it inside completion of action? (or is some better way how to write to the Realm database without do-try-catch block?)
Short answer is no, there is no way to write data to realm without write transaction and without try-catch.
realm.write() is a convenient wrapper of transaction building with beginWrite() and commitWrite() calls.
These two functions build a transaction and commitWrite() is throwable, so you need to wrap to try-catch, anyway.
See https://realm.io/docs/swift/latest#writes
Example of using beginWrite()+commitWrite() https://realm.io/docs/swift/latest#interface-driven-writes
There are a lot of failures could happen during write transactions. So, simply, it is not safe to not to handle it somehow.
Also grouping write transactions by "action" is not a good idea if you going to process big amounts of objects because write transactions are costly. You'd rather group these changes to a single transaction instead of having a lot of small transactions.

How do I handle async functions in a loop?

I'm looping through a table's rows, for each of them I'm doing a couple of async calls like fetching data from API, copying files, running shell script... How do I wait for the result until going to the next one.
Also I'm new to Swift, not sure if this is the best way to handle a group of async tasks. Should I use concurrency in this case ?
tableView.selectedRowIndexes.forEach { row in
myData.fetch(url: urlList[row]) { res in
self.anotherAsyncCall(res) { data in
//continue to deal with next row now
}
}
}
If you really want to do this sequentially, the easiest way is to perform your tasks recursively, actually invoking the next task in the completion handler of the prior one:
processNext(in: tableView.selectedRowIndexes) {
// do something when they're all done
}
Where:
func processNext(in rows: [Int], completion: #escaping () -> Void) {
guard let row = rows.first else {
completion()
return
}
myData.fetch(url: urlList[row]) { res in
self.anotherAsyncCall(res) { data in
//continue to deal with next row now
self.processNext(in: Array(rows.dropFirst()), completion: completion)
}
}
}
But I agree with GoodSp33d that the other approach is to wrap this asynchronous process in a custom, asynchronous, Operation subclass.
But this begs the question why you want to do these sequentially. You will pay a significant performance penalty because of the inherent network latency for each request. So the alternative is to let them run concurrently, and use dispatch group to know when they're done:
let group = DispatchGroup()
tableView.selectedRowIndexes.forEach { row in
group.enter()
myData.fetch(url: urlList[row]) { res in
self.anotherAsyncCall(res) { data in
//continue to deal with next row now
group.leave()
}
}
}
group.notify(queue: .main) {
// do something when they're all done
}
Whether you can run these concurrently (or to what degree) is a function of what you're doing inside various asynchronous methods. But I would suggest you think hard about making this work concurrently, as the performance is likely to be much better.
If you are using some promise library, just use the all function.
Here is some Document about promise.all()
And PromiseKit use when instead,
you can read about the faq and the tutorial about when for more information.
If you want to do that without any promise library, here is the pseudocode:
var results = []
rows.forEach {row in
fetch(row) {res in
results.push(res)
if(results.length == rows.length) {
// do something using the results here
}
}
}

Observable being disposed ahead of time

I think it's better if I explain what I'm trying to achieve because I think the error is on my misunderstanding on how Observables work.
I have a UIViewController that contains a UITableView I'm also using RxSwift and RxDataSources, so I'm binding my tableView items like this:
vm.model
.debug()
.drive(tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
Where vm is a viewModel which contains:
self.model = self.network.provider.getMarkets()
.map { (markets: [Market]) -> [Row] in
var rows = [Row]()
for market in markets {
rows.append(.market(market: market))
}
return rows
}
.map { (rows: [Row]) -> [Model] in
return [Model(section: .market, items: rows)]
}
.shareReplay(1)
.asDriver(onErrorJustReturn: [])
Where model is:
var model: Driver<[Model]>
This all works great the first time, the tableview displays the items, but the print from the debug():
2017-04-28 20:07:21.382: MarketAndLanguageSelectionViewController.swift:36 (viewDidLoad()) -> subscribed
2017-04-28 20:07:22.287: MarketAndLanguageSelectionViewController.swift:36 (viewDidLoad()) -> Event next(*Multiple items*)
2017-04-28 20:07:22.289: MarketAndLanguageSelectionViewController.swift:36 (viewDidLoad()) -> Event completed
2017-04-28 20:07:22.289: MarketAndLanguageSelectionViewController.swift:36 (viewDidLoad()) -> isDisposed
The problem is I didn't want the datasource to dispose because I wan't to update it based on the user action. If the user clicks a tableViewCell I want to update the model. Any ideas on how can I achieve this?
Sorry for such a big question.
I'm guessing that network.provider.getMarkets() makes a network call which returns a single result and completes.
Now, getMarkets() is the source, and tableView.rx.items is the sink. Once the source completes, the chain is broken.
It sounds like what you want to do is create a new getMarkets Observable every time the user taps something, as well as calling getMarkets once for free. I would expect something like:
let markets = trigger.flatMap {
self.network.provider.getMarkets()
}.map { (markets: [Market]) -> [Row] in
var rows = [Row]()
for market in markets {
rows.append(.market(market: market))
}
return rows
}.map { (rows: [Row]) -> [Model] in
return [Model(section: .market, items: rows)]
}.startWith(self.network.provider.getMarkets())
.shareReplay(1)
.asDriver(onErrorJustReturn: [])
Note that the only real difference is the beginning trigger.flatMap {. Your source will then be the button or whatever the user taps on to cause the network update which won't complete until it's deleted.
(The above is untested code, but it should give you an idea of the shape you want.)

Wait for Parse Async functions to complete in Swift

I'm trying to wait for Parse async functions in Swift to reload my UITableView
I'm not sure if Completion Handler is useful in this case. or Dispatch Async.
I'm really confused ! Can someone help out with this
var posts = [PFObject]()
for post in posts {
post.fetchInBackground()
}
tableView.reloadData() // I want to execute that when the async functions have finished execution
You want to use fetchAllInBackground:Block I've had issues launching a bunch of parse calls in a loop where it will take a lot longer to return all of them than expected.
fetch documentation
It should look something like this:
PFObject.fetchAllInBackground(posts, block: { (complete, error) in
if (error == nil && complete) {
self.tableView.reloadData()
}
})
One thing to note is that in your example posts are empty and a generic PFObject. I'm assuming this is just for the example. Otherwise if you want to get all posts in Parse (as opposed to updating current ones) you will want to use PFQuery instead of fetching. query documentation
You need to use fetchInBackgroundWithBlock. Alternatively, if you want to wait until all have loaded and then update the UI, use PFObject's +fetchAllInBackground:block:. Note that this is a class method, and would therefore be called as PFObject.fetchAllInBackground(.... See documentation here.
Either way, because you're running in a background thread, you must update the UI on the main thread. This is normally done using dispatch_async.
The other thing to watch out for is if you run fetchInBackgroundWithBlock in a loop and collect all the results in an array, arrays are not thread safe. You will have to use something like dispatch_barrier or your own synchronous queue to synchronise access to the array. Code for the second option is below:
// Declared once and shared by each call (set your own name)...
let queue = dispatch_queue_create("my.own.queue", nil)
// For each call...
dispatch_sync(queue) {
self.myArray.append(myElement)
}
Here's a little class I made to help with coordination of asynchronous processes:
class CompletionBlock
{
var completionCode:()->()
init?(_ execute:()->() )
{ completionCode = execute }
func deferred() {}
deinit
{ completionCode() }
}
The trick is to create an instance of CompletionBlock with the code you want to execute after the last asynchronous block and make a reference to the object inside the closures.
let reloadTable = CompletionBlock({ self.tableView.reloadData() })
var posts = [PFObject]()
for post in posts
{
post.fetchInBackground(){ reloadTable.deferred() }
}
The object will remain "alive" until the last capture goes out of scope. Then the object itself will go out of scope and its deinit will be called executing your finalization code at that point.
Here is an example of using fetchInBackgroundWithBlock which reloads a tableView upon completion
var myArray = [String]()
func fetchData() {
let userQuery: PFQuery = PFUser.query()!
userQuery.findObjectsInBackgroundWithBlock({
(users, error) -> Void in
var userData = users!
if error == nil {
if userData.count >= 1 {
for i in 0...users!.count-1 {
self.myArray.append(userData[i].valueForKey("dataColumnInParse") as! String)
}
}
self.tableView.reloadData()
} else {
print(error)
}
})
}
My example is a query on the user class but you get the idea...
I have experimented a bit with the blocks and they seem to get called on the main thread, which means that any UI changes can be made there. The code I have used to test looks something like this:
func reloadPosts() {
PFObject.fetchAllIfNeededInBackground(posts) {
[unowned self] (result, error) in
if let err = error {
self.displayError(err)
}
self.tableView.reloadData()
}
}
if you are in doubt about whether or not the block is called on the main thread you can use the NSThread class to check for this
print(NSThread.currentThread().isMainThread)
And if you want it to be bulletproof you can wrap your reloadData inside dispatch_block_tto ensure it is on the main thread
Edit:
The documentation doesn't state anywhere if the block is executed on the main thread, but the source code is pretty clear that it does
+ (void)fetchAllIfNeededInBackground:(NSArray *)objects block:(PFArrayResultBlock)block {
[[self fetchAllIfNeededInBackground:objects] thenCallBackOnMainThreadAsync:block];
}