I've got 2 basic methods - viewDidLoad and viewDidAppear. According to my App philosophy, when view controller loads, it fetches data from base and starts to sort it with some predicates. Fetching process is long, so I dispatched it to global queue. When my view appears, it obviously do not get the value from array(which compiles in load method) and crashes. So I need viewDidAppear to wait till at least one object will be appended to array.
Kind of semaphores or temp values?
Thanks in advance!
P.S. Each item in array represent struct with data which composes UI. User interact with this UI, so it has to be loaded once with the first item from array. To switch to next item, user just clicks "next" and UI changes according to next item from array. That's why I want the data to fetch in background and allow user to work immediately. (It's impossible to jump on 5th, 10th or 1001st element immediately, there will be enough time to fetch data before user gets on these page numbers)
P.P.S Still no right decision :(
You should using a nested dispatch block, like so:
func fetch(completion block:(() -> Void)?) {
// Run fetch on background thread, to prevent the main thread (and hence your UI) from being 'blocked'.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
//
// Fetch data...
//
dispatch_async(dispatch_get_main_queue(), {
block?()
})
})
}
fetch(completion: {
// Update your UI
})
Related
This seems like it ought to have a very simple answer.
I am building an app using SwiftUI and firebase realtime database.
The database will have a node called items with a large number of children - on the order of 1,000.
I'd like the app to load the contents of that node children when it launches, then listen to firebase for future additions of children. Imagine the following:
struct Item { … } // some struct
var items: [Item] = []
let itemsRef = Database.database().reference().child("items")
According to firebase, this call ought to load in all the children one at a time, and then add new ones as they are added to the items node in firebase:
itemsRef.observe(.childAdded) { snapshot in
// add a single child based on snapshot
items.append(Item(fromDict: snapshot.value as! [String:String])
}
That gets the job done, but seems hugely inefficient compared to using the getData() method they supply, which hands us a dictionary containing all the children:
itemsRef.getData() { error, snapshot in
// set all children at once base on snapshot
items = Item.itemArray(fromMetaDict: snapshot.value as! [String:[String:String]])
}
It would seem best to use getData() initially, then observe(.childAdded) to monitor additions. But then how do we prevent the observe completion block from running 1,000 times when it fires up? The firebase docs say that that's what will happen:
This event is triggered once for each existing child and then again every time a new child is added to the specified path.
Thanks in advance!
PS I didn't think it necessary to include definitions for the functions Item.init(fromDict:) or Item.itemArray(fromMetaDict:) — hopefully it's clear what they are meant to do.
There is (or: should be) no difference between listening for .childAdded on a path versus listening for .value or calling `getData() on that same path. The distinction is purely client-side and the wire traffic is (or: should be) the same.
It is in fact quite common to listen for .child* events to manipulate some data structure/UI, and then also listen for the .value event to commit those changes, as .value is guaranteed to fire after all corresponding .child*.
A common trick to do the first big batch of data in .value or getData(), and then use .child* for granular updates is to have a boolean flag to indicate whether you got the initial data already, and set if to false initially.
In the .child* handlers, only process the data if the flag is true.
itemsRef.observe(.childAdded) { snapshot in
if isInitialDataProcessed {
items.append(Item(fromDict: snapshot.value as! [String:String])
}
}
And then in your .value/getData handler, process the data and then set the flag to true.
I am instantiating a URLSessionDataTask that downloads hundreds of thumbnail images and stuffs them into an array. Each entry in the array is used to populate a cell in a UITableView instance. It works exactly as expected.
However, I want to give the user the opportunity to click on a cell and initiate a second instance of a URLSessionDataTask in an effort to download additional details associated with that thumbnail. And I don't want the user to wait until the first data task finishes.
That's where the problem lies. The second URLSessionDataTask instance doesn't retrieve the data I need until the first URLSessionDataTask instance completes. I guess I don't understand this since my understanding is that the first task is an asynchronous background task.
So I tried a workaround such that when the user clicks on a cell to grab the detail information I would suspend the first task, download the detail info and then resume the first task.
I want to do something roughly like this:
var firstTask = callThumbnailServer(queryString: createHTTPQueryString())
func userAsksForDetails() {
firstTask.suspend()
var secondTask = callDetailServer(queryString: createHTTPQueryString())
firstTask.resume()
}
But firstTask.suspend() appears to have no effect. Now, firstTask.cancel() does successfully cancel the first task, but I don't want to cancel, I want to suspend/resume.
So I guess I have two questions:
Why does the second data task appear not to run until the first one completes?
Why does cancel() work but suspend() does not?
Sorry if these are dumb questions, I'm just starting with UIKit and Swift.
I want to check if a pdf file is changed or not, and if is changed i want to update the corresponding view. I don't know if it's more suitable to use a background process as a Thread or as an NSOperation to do this task. The Apple Documentation says: "Examples of tasks that lend themselves well to NSOperation include network requests, image resizing, text processing, or any other repeatable, structured, long-running task that produces associated state or data.But simply wrapping computation into an object doesn’t do much without a little oversight".
Also, if I understood correctly from the documentation, a Thread once started can't be stopped during his execution while an NSOperation could be paused or stopped and also they could rely on dependency to wait the completion of another task.
The workflow of this task should be more or less this diagram:
Task workflow
I managed to get the handler working after the notification of type .write has been sent. If i monitor for example a *.txt file everything works as expected and i receive only one notification. But i am monitoring a pdf file which is generated from terminal by pdflatex and thus i receive with '.write' nearly 15 notification. If i change to '.attrib' i get 3 notification. I need the handler to be called only once, not 15 or 3 times. Do you have any idea how can i do it or is not possible with a Dispatch Source? Maybe there is a way to execute a dispatchWorkItem only once?
I have tried to implement it like this(This is inside a FileMonitor class):
func startMonitoring()
{
....
let fileSystemRepresentation = fileManager.fileSystemRepresentation(withPath: fileStringURL)
let fileDescriptor = open(fileSystemRepresentation, O_EVTONLY)
let newfileMonitorSource = DispatchSource.makeFileSystemObjectSource(fileDescriptor: fileDescriptor,
eventMask: .attrib,
queue: queue)
newfileMonitorSource.setEventHandler(handler:
{
self.queue.async
{
print(" \n received first write event, removing handler..." )
self.newfileMonitorSource.setEventHandler(handler: nil)
self.test()
}
})
self.fileMonitorSource = newfileMonitorSource
fileMonitorSource!.resume()
}
func test()
{
fileMonitorSource?.cancel()
print(" restart monitoring ")
startMonitoring()
}
I have tried to reassign the handler in test(), but it's not working(if a regenerate the pdf file, what is inside the new handler it's not executed) and to me, doing in this way, it seems a bit boilerplate code. I have also tried the following things:
suspend the DispatchSource in the setEventHandler of startMonitoring() (passing nil), but then when i am resuming it, i get the remaining .write events.
cancel the DispatchSource object and recall the startMonitoring() as you can see in the code above, but in this way i create and destroy the DispatchSource object everytime i receive an event, which i don't like because the cancel() function shoul be called in my case only when the user decide to disable this feauture i am implementing.
I will try to write better how the workflow of the app should be so you can have an more clear idea of what i am doing:
When the app starts, a functions sets the default value of some checkboxes of the window preference. The user can modify this checkboxes. So when the user open a pdf file, the idea is to launch in a background thread the following task:
I create a new queue call it A and launch asynch an infinite while where i check the value of the UserDefault checkboxe (that i use to reload and update the pdf file) and two things could happen
if the user set the value to off and the pdf document has been loaded there could be two situations:
if there is no current monitoring of the file (when the app starts): continue to check the checkboxe value
if there is currently a monitoring of the file: stop it
if the user set value to on and the pdf document has been loaded in this background thread (the same queue A) i will create a class Monitor (that could be a subclass of NSThread or a class that uses DispatchSourceFileSystemObject like above), then i will call startMonitoring() that will check the date or .write events and when there is a change it will call the handler. Basically this handler should recall the main thread (the main queue) and check if the file can be loaded or is corrupted and if so update the view.
Note: The infinite while loop(that should be running in the background), that check the UserDefault related to the feature i am implementing it's launched when the user open the pdf file.
Because of the problem above (multiple handlers calls), i should use the cancel() function when the user set checkboxe to off, and not create/destroy the DispatchSource object everytime i receive a .write event.
I'm seeing crashes that either shouldn't be possible, or are very much possible and the documentation just isn't clear enough as to why.
UPDATE:
Although I disagree with the comment below asking me to separate this into multiple SO questions, if someone could focus on this one I think it would help greatly:
When are notifications delivered to the main thread? Is it possible that the results on the main thread are different than they were in a previous runloop without being notified yet of the difference?
If the answer to this question is yes the results could be different than a previous runloop without notifying then I would argue it is CRUCIAL to get this into the documentation somewhere.
Background Writes
First I think it's important to go over what I am already doing for writes. All of my writes are performed through a method that essentially looks like this (error handling aside):
func write(block: #escaping (Realm) -> ()) {
somePrivateBackgroundSerialQueue.async {
autoreleasepool {
let realm = try! Realm()
realm.refresh()
try? realm.write { block(realm) }
}
}
}
Nothing crazy here, pretty well documented on your end.
Notifications and Table Views
The main question I have here is when are notifications delivered to the main thread after being written from a background thread? I have a complex table view (multiple sections, ads every 5th row) backed by realm results. My main data source looks like:
enum StoryRow {
case story(Story) // Story is a RealmSwift.Object subclass
case ad(Int)
}
class StorySection {
let stories: Results<Story>
var numberOfRows: Int {
let count = stories.count
return count + numberOfAds(before: count)
}
func row(at index: Int) -> StoryRow {
if isAdRow(at: index) {
return .ad(index)
} else {
let storyIndex = index - numberOfAds(before: index)
return .story(stories[storyIndex])
}
}
}
var sections: [StorySection]
... sections[indexPath.section].row(at: indexPath.row) ...
Before building my sections array I fetch the realm results and filter them based on the type of stories for the particular screen, sort them so they are in the proper order for their sections, then I build up the sections by passing in results.filter(...date query...) to the section constructor. Finally, I results.observe(...) the main results object (not any of the results passed into the section) and reload the table view when the notification handler is called. I don't bother observing the results in the sections because if any of those results changed then the parent had to change as well and it should trigger a change notification.
The ad slots have callbacks when an ad is filled or not filled and when that happens instead of calling tableView.reloadData() I am doing something like:
guard tableView.indexPathsForVisibleRows?.contains(indexPath) == true else { return }
tableView.beginUpdates()
tableView.reloadRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
The problem is, I very rarely see a crash either around an index being out of bound when accessing the realm results or an invalid table view update.
QUESTIONS
Is it possible the realm changed on the main thread before any notifications were delivered?
Should table view updates other than reloadData() simply not be used anywhere outside of a realm notification block?
Anything else crucial I am missing?
There's nothing in your code snippets or description of what you're doing that jumps out at me as obviously wrong. Having a separate callback mechanism that updates specific slots independent of Realm change notifications has a lot of potential for timing related bugs, but since you're explicitly checking if the indexPath is visible before reloading the row, I would expect that to at worst manifest as reloading the wrong row and not a crash.
The intended behavior is that refreshing the Realm and delivering notifications is an atomicish operation: anything that causes the read version to advance will deliver all notifications before returning. In simple cases, this means that you'll never see the new data without the associated notification firing first. However, there's some caveats to this:
Nested notification delivery doesn't work correctly, so beginning a write transaction from within a notification block can result in a notification being skipped (merely calling refresh() can't cause this, as it's just a no-op within a notification). If you're performing all writes on background threads you shouldn't be hitting this.
If you have multiple notification blocks, then obviously anything which gets invoked from the first one will run before the second notification block gets a chance to do things, and a call to tableView.reloadData() may result in quite a lot of things happening within the notification block. If this is the source of problems, you would hopefully see exceptions being thrown with a stack trace coming from within a notification block.
In my app I use two contexts: app delegate main context and a private context.
The private context is set as follows:
var privateContext: NSManagedObjectContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType)
privateContext.persistentStoreCoordinator = context.persistentStoreCoordinator
I also set an observer on the private context to trigger a save via main context:
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(MyView.managedObjectContextDidSave(_:)), name: NSManagedObjectContextDidSaveNotification, object: self.privateContext)
I trigger a data download with a callback to the current ViewController. During the download, I process all objects in:
privateContext.performBlock {
// process objects
....
// now save
if self.privateContext.hasChanges {
privateDataManager.save()
}
}
The save in the private context, triggers the observer and this code gets invoked:
dispatch_async(AppUtils.GlobalMainQueue, {
self.context.mergeChangesFromContextDidSaveNotification(notification)
})
The problem is that every now and then, not all changes get persisted. Cannot say when or why - when I debug it it always works...
How do I know I have a problem? Well, I compare object count before and after the download. To add more 'colour':
Every download of data adds some new records. The app then selects which records are out-of-date and marks them for deletion (sets a flag on a record). It then saves them (new and 'to be deleted') in the private context. The 'save' triggers a 'merge into primary context' notification.
In theory, this notification triggers the 'merge' in a synchronous manner.
After the merge, assuming it does happen in-order, there is data reload - this reload only loads records that do not have the 'deleted' flag set.
The problem I am having is that the 'merge' does not seem to always happen before I reload (that's the only way I can explain this).
My question, assuming my analysis is correct, is how to force the merge to happen before the reload? Does the notification not happen in a synchronous manner?
I guess I could always save the private context and instead of triggering the notification, simply create a notification object using private context and force trigger the merge. But I would like to understand why the above code does not work as expected.
The main source on which I based my code can be found here.