Background Process as NSOperation or Thread to monitor and update File - 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.

Related

Unable to suspend an instance of a URLSessionDataTask

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.

Managing multiple events for the same file change generated by pdflatex with a DispatchQueue

I am currently using a DispatchQueue and DispatchSourceFileSystemObject for tracking file changes on a pdf file which is generated by pdflatex. The problem i have is that when pdflatex generate the pdf, it sends several '.write' events and not only one. The job of the handler is to update the corresponding view where the pdf is displayed and i want to avoid to update several times the view with basically the same change(the handler is called every time an event of a specific flag is received). I want to call the handler only once and when the last '.write' event of the same pdf generation occur. For example if the pdflatex produce 10 '.write' events, the handler should be called only when the tenth event has been received.
I have tried to:
check with a flag the current event received to ignore future event with the same flag, and then sleep for some seconds to wait until i receive all the '.write' events but this is not a solution, because depending of the pdf file to generate it could take different time for the process pdflatex to complete.
get the modification date of the file, but with several '.write' sometime the date is the same for every '.write' and sometime it changes for 1 second etc.. so using the Date as a way to call the handler is not a good idea.
I am using a serial queue so the operation are not concurrent in the same queue. I would like, if possible, to continue to use the DisaptchQueue and only if there is no solution i would also appreciate a possible implementation with OperationQueue and Operation or BlockOperation.
I Solved the problem in this way: every time i receive a '.write' event, i try to build a pdfDocument with his url, if the document is nil it means the pdflatex process is still sending '.write' events. In the last '.write' event i can successfully get a pdfDocument because the document has been completed.

CoreData, threading, main context and private context - works 99% of the time but

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.

Simple waiting for value from async thread

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
})

Executing operations one after the other

I am struggeling with something and I would like to ask you if you could point me in the right direction.
I have four tasks I want to complete, -one after the other.
Fetch html-code from web
Parse this code and save to core data storage
Use this data and batch save to calendar
Upload the parsed data to my own web server.
I have written all the code for this and it executes fine. However, at times it struggles as some of the code is executed before the other has finished.
Example:
func startProcess () {
fetchHTMLFromWeb()
parseHTML()
saveToCalendar()
//Sometimes uploadToWeb() starts before saveToCalendar() is finished
uploadToWeb()
}
I have tried reading up on GCD, but it is a rather complex subject and I am finding it hard to grasp it.
Can you recommend any good readups on this subject?
Thank you very much!
You can use the GCD to execute all your stuffs in the background queue.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
self.startProcess();
});
with that, startProcess will start on the background queue/thread. So you can
In the fetchHtmlFromWeb method just call parseHtml(), when the fetch is ended.
hope it helps.