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.
Related
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.
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
})
This is all purely for educational purposes, to help me get a better understanding of how Parse as well as Swift operates.
I would like to make it so a user is only able to like an item once (not being able to hit a button multiple times), as currently, I'm utilizing an anonymous system with Parse.
Would I essentially use an if method with PFUser.CurrentUser() in the likeButton method to halt a user from hitting like again or would I use NSUserDefaults?
I'm not able to post code currently as I'm not near my laptop, however I could later if it helps. Still curious if I could get some info before that however.
Sample code I found on here from a previous question, which essentially implements the same idea.
#IBAction func likeButton(sender: AnyObject) {
let hitPoint = sender.convertPoint(CGPointZero, toView: self.tableView)
let hitIndex = self.tableView.indexPathForRowAtPoint(hitPoint)
let object = objectAtIndexPath(hitIndex)
object.incrementKey("count")
object.saveInBackground()
self.tableView.reloadData()
}
Would I call NSUsersDefaults to stunt the user from hitting it more than once?
Instead of calling saveInBackground(), you'd better call the method saveInBackgroundWithBlock: instead. So the strategy is very simple:
First of all, define a 'busy' flag for the object (For example: savingInBackground) and store it wherever you like ( If you are showing 1 item then simply declare a Bool property / If you are showing a list of Item then declare a Dictionary with format ["objectID/Index": Bool]). This flag should be set to true for the item being saved in the background.
Whenever use taps on a Like button
If current item's savingInBackground flag is true, then do nothing
Else:
Set item's savingInBackground to true
Increase Like count and Call saveInBackgroundWithBlock:
In the completion block of saveInBackgroundWithBlock:, set savingInBackground back to false.
I am on train now so I can't write example code, but I hope that it's clear enough to help you to achieve what you want.
I added the ORKOrderedTask.fitnessCheckTaskWithIdentifier Task and it renders find in the UI. But unlike other simpler tasks containing scale/choice/date questions, I was not able to find the exact way to read the sensor data collected via ORKOrderedTask.fitnessCheckTaskWithIdentifier.
I have used the following:
private var walkingTask : ORKTask {
return ORKOrderedTask.fitnessCheckTaskWithIdentifier("shortWalkTask", intendedUseDescription: "Take a short walk", walkDuration: 10, restDuration: 5, options: nil)
}
upon task completion the task view controller delegate below is hit.
//ORKTaskViewControllerDelegate
func taskViewController(taskViewController: ORKTaskViewController, didFinishWithReason reason: ORKTaskViewControllerFinishReason, error: NSError?)
is there a way to drill down into the result object contained in task view controller (taskViewController.result) to get the step count? Or will i have to go through health kit or something and then query the required observation? Request help from anyone who has used this task before and can provide some input on how to fetch the pedometer data (step count specifically) for the duration the task was active?
I'm using swift.
The step count is not reflected in the result objects per se. Instead, one of the child ORKFileResult objects, generated from the pedometer recorder, will contain the pedometer records queried from CoreMotion, serialized to JSON.
However, exposing the step count on a result object, sounds like a useful extension / improvement, and we should see if it generalizes to other recorders too. Please open an issue on GitHub and we will see what we can do!