Can I cancel a JSONEncoder Swift? - swift

I have a JSONEncoder encoding a 20mb file, which takes ages to process. If the data it's processing changes, I'd like to cancel the encoding, and restart the encoding process but I can't think of a way to do this. Any ideas?
I could call JSONEncoder.encode again, but now I would have two 30 second processes running, and double the amount of memory and processor overhead.
It would be lovely to be able cancel the previous one.
EDIT: Some of you requested to see my encoder. Here's the one which I'd say causes the biggest bottleneck...
func encode(to encoder: Encoder) throws {
try autoreleasepool {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(brush, forKey: .brush)
if encoder.coderType == CoderType.export {
let bezierPath = try NSKeyedUnarchiver.unarchivedObject(ofClass: UIBezierPath.self, from: beziersData)
let jsonData = try UIBezierPathSerialization.data(with: bezierPath, options: UIBezierPathWritingOptions.ignoreDrawingProperties)
let bezier = try? JSONDecoder().decode(DBBezier.self, from: jsonData)
try container.encodeIfPresent(bezier, forKey: .beziersData)
} else {
try container.encodeIfPresent(beziersData, forKey: .beziersData)
}
}
}

You can use OperationQueue and add your long running task into that operation queue.
var queue: OperationQueue?
//Initialisation
if queue == nil {
queue = OperationQueue()
queue?.maxConcurrentOperationCount = 1
}
queue?.addOperation {
//Need to check the isCanceled property of the operation for stopping the ongoing execution in any case.
self.encodeHugeJSON()
}
You can also cancel the task whenever you want using the following code:
//Whenever you want to cancel the task, you can do it like this
queue?.cancelAllOperations()
queue = nil
What is an Operation Queue:
An operation queue invokes its queued Operation objects based on their
priority and readiness. After you add an operation to a queue, it
remains in the queue until the operation finishes its task. You can’t
directly remove an operation from a queue after you add it.
Reference links:
https://developer.apple.com/documentation/foundation/operationqueue
https://www.hackingwithswift.com/example-code/system/how-to-use-multithreaded-operations-with-operationqueue

Related

How do I asynchronously initialize firebase firestore listeners while also knowing when all of the tasks are done?

Basically, at the launch of my app, I want to load the latest data from firebase from about 4-5 different documents. Then I also want to set up a listener to monitor data changes. I do this by calling 4-5 similar functions that take a dispatchGroup as an argument. I may be approaching this completely wrong but I could not think of any other way to do it. I just want to load those documents, set up listeners, and take certain action whenever those docs are loaded at the launch of the app.
// app launch
let dispatch = DispatchGroup()
getFirebaseDocument1(dispatch: dispatch)
getFirebaseDocument2(dispatch: dispatch)
getFirebaseDocument3(dispatch: dispatch)
getFirebaseDocument4(dispatch: dispatch)
getFirebaseDocument5(dispatch: dispatch)
dispatch.notify(queue:main) {
// execute some code to execute after all the documents are fetched
}
// typical getFirebaseDocument code
dispatch.enter()
let ref = someFirestoreReference
ref.addSnapshotListener { (snapshot, error) in
if let error = error{
// handle error
} else {
// load the document
}
dispatch.leave()
}
The code works fine when it's launched but crashes whenever the listener receives an update. I know this is because the dispatch.leave() is called in the listener function. However, I cannot seem to figure out a clever solution to where I can asynchronously load the data from firebase at launch while also setting up listeners. I would also prefer not to nest closures within one another as it wouldn't be asynchronous and it would also be a pain.
I may be wrong, but you should leave the group inside your block, here is the example how I do it using group
class func getRates(completion: #escaping EmptyBlock) {
let eurRequest = APIConfigs.request(part: "rs/price/history")
let usdRequest = APIConfigs.request(part: "rs/price/history/usd")
let group = DispatchGroup()
group.enter()
sendRequest(request: eurRequest, method: .get, parameters: ServerParameters.rates()) { response in
Course.current.addRate(rates: ratesRequest(response: response), type: .eur)
group.leave()
}
group.enter()
sendRequest(request: usdRequest, method: .get, parameters: ServerParameters.rates()) { response in
Course.current.addRate(rates: ratesRequest(response: response), type: .usd)
group.leave()
}
group.notify(queue: .main) {
completion()
}
}

How does the semaphore keep async loop in order?

I've set up this script to loop through a bunch of data in the background and I've successfully set up a semaphore to keep everything (the array that will populate the table) in order but I cannot exactly understand how or why the semaphore keeps the array in order. The dispatchGroup is entered, the loop stops and waits until the image is downloaded, once the image is gotten the dispatchSemaphore is set to 1 and immediately the dispatchGroup is exited and the semaphore set back to 0. The semaphore is toggled so fast from 0 to 1 that I don't understand how it keeps the array in order.
let dispatchQueue = DispatchQueue(label: "someTask")
let dispatchGroup = DispatchGroup()
let dispatchSemaphore = DispatchSemaphore(value: 0)
dispatchQueue.async {
for doc in snapshot.documents {
// create data object for array
dispatchGroup.enter()
// get image with asynchronous completion handler
Storage.storage().reference(forURL: imageId).getData(maxSize: 1048576, completion: { (data, error) in
defer {
dispatchSemaphore.signal()
dispatchGroup.leave()
}
if let imageData = data,
error == nil {
// add image to data object
// append to array
}
})
dispatchSemaphore.wait()
}
// do some extra stuff in background after loop is done
}
dispatchGroup.notify(queue: dispatchQueue) {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
The solution is in your comment get image with asynchronous completion handler. Without the semaphore all image downloads would be started at the same time and race for completion, so the image that downloads fastest would be added to the array first.
So after you start your download you immediately wait on your semaphore. This will block until it is signaled in the callback closure from the getData method. Only then the loop can continue to the next document and download it. This way you download one file after another and block the current thread while the downloads are running.
Using a serial queue is not an option here, since this would only cause the downloads to start serially, but you can’t affect the order in which they finish.
This is a rather inefficient though. Your network layer probably can run faster if you give it multiple requests at the same time (think of parallel downloads and HTTP pipelining). Also you're 'wasting' a thread which could do some different work in the meantime. If there is more work to do at the same time GCD will spawn another thread which wastes memory and other resources.
A better pattern would be to skip the semaphore, let the downloads run in parallel and store the image directly at the correct index in your array. This of course means you have to prepare an array of the appropriate size beforehand, and you have to think of a placeholder for missing or failed images. Optionals would do the trick nicely:
var images: [UIImage?] = Array(repeating: nil, count: snapshot.documents.count)
for (index, doc) in snapshot.documents.enumerated() {
// create data object for array
dispatchGroup.enter()
// get image with asynchronous completion handler
Storage.storage().reference(forURL: imageId).getData(maxSize: 1048576) { data, error in
defer {
dispatchGroup.leave()
}
if let imageData = data,
error == nil {
// add image to data object
images[index] = image
}
}
}
The DispatchGroup isn't really doing anything here. You have mutual exclusion granted by the DispatchSemaphor, and the ordering is simply provided by the iteration order of snapshot.documents

Swift loadItem closure not running

I am writing a share extension, but my closure that would capture and save the shared attachment is not running. How can I find out why? The switch branch executes, the attachment is there. There is no error message, it just never runs.
if let contents = content.attachments as? [NSItemProvider] {
for attachment in contents {
let fType = attachment.registeredTypeIdentifiers[0]
if attachment.hasItemConformingToTypeIdentifier(fType) {
switch fType {
case kUTTypeImage as String as String:
do {
attachment.loadItem(forTypeIdentifier: fType, options: nil, completionHandler: { data, error in
print("AppImage")
let url = data as! URL
if let imageData = try? Data(contentsOf: url) {
self.appImage = UIImage(data: imageData)
self.saveImage(image: self.appImage!)
}
})
} // public image case
Once completeRequestReturningItems (CRRI) is executed, your completion handlers for loadItem will no longer be called (they are effectively canceled at that point). Therefore you must synchronize your asynchronous tasks to ensure that you don't execute CRRI until your completion handlers have finished or until you no longer care. From your comments, it sounds like you are invoking loadItem and immediately proceeding to call CRRI.
See answers to this related question: iOS 8 Share extension loadItemForTypeIdentifier:options:completionHandler: completion closure not executing
I prefer the answer there that uses a dispatch group.

How to stop DispatchGroup or OperationQueue waiting?

DispatchGroup and OperationQueue have methods wait() and waitUntilAllOperationsAreFinished() which wait for all operations in respective queues to complete.
But even when I call cancelAllOperations it just changes the flag isCancelled in every running operation and stop the queue from executing new operations. But it still waits for the operations to complete. Therefore running the operations must be stopped from the inside. But it is possible only if operation is incremental or has an inner cycle of any kind. When it's just long external request (web request for example), there is no use of isCancelled variable.
Is there any way of stopping the OperationQueue or DispatchGroup waiting for the operations to complete if one of the operations decides that all queue is now outdated?
The practical case is: mapping a request to a list of responders, and it is known that only one may answer. If it happens, queue should stop waiting for other operations to finish and unlock the thread.
Edit: DispatchGroup and OperationQueue usage is not obligatory, these are just tools I thought would fit.
OK, so I think I came up with something. Results are stable, I've just tested. The answer is just one semaphore :)
let semaphore = DispatchSemaphore(value: 0)
let group = DispatchGroup()
let queue = DispatchQueue(label: "map-reduce", qos: .userInitiated, attributes: .concurrent)
let stopAtFirst = true // false for all results to be appended into one array
let values: [U] = <some input values>
let mapper: (U) throws -> T? = <closure>
var result: [T?] = []
for value in values {
queue.async(group: group) {
do {
let res = try mapper(value)
// appending must always be thread-safe, otherwise you end up with race condition and unstable results
DispatchQueue.global().sync {
result.append(res)
}
if stopAtFirst && res != nil {
semaphore.signal()
}
} catch let error {
print("Could not map value \"\(value)\" to mapper \(mapper): \(error)")
}
}
}
group.notify(queue: queue) { // this must be declared exactly after submitting all tasks, otherwise notification fires instantly
semaphore.signal()
}
if semaphore.wait(timeout: .init(secondsFromNow: 5)) == .timedOut {
print("MapReduce timed out on values \(values)")
}

Swift - Semaphore within semaphore [duplicate]

I'm entering the concurrency programming with some semaphore issues.
My function first loads data from server, analyze received info and then, if necessary, makes second request to server.
I tried different ways to make it run, none of them did it well.
My current code FOR ME seems to be correct, but on second request it just locks(maybe like a DeadLock) and the last log is "<__NSCFLocalDataTask: 0x7ff470c58c90>{ taskIdentifier: 2 } { suspended }"
Please, tell me what do I don't know. Maybe there is more elegant way to work with completions for these purposes?
Thank you in advance!
var users = [Int]()
let linkURL = URL.init(string: "https://bla bla")
let session = URLSession.shared()
let semaphore = DispatchSemaphore.init(value: 0)
let dataRequest = session.dataTask(with:linkURL!) { (data, response, error) in
let json = JSON (data: data!)
if (json["queue"]["numbers"].intValue>999) {
for i in 0...999 {
users.append(json["queue"]["values"][i].intValue)
}
for i in 1...lround(json["queue"]["numbers"].doubleValue/1000) {
let session2 = URLSession.shared()
let semaphore2 = DispatchSemaphore.init(value: 0)
let linkURL = URL.init(string: "https://bla bla")
let dataRequest2 = session2.dataTask(with:linkURL!) { (data, response, error) in
let json = JSON (data: data!)
print(i)
semaphore2.signal()
}
dataRequest2.resume()
semaphore2.wait(timeout: DispatchTime.distantFuture)
}
}
semaphore.signal()
}
dataRequest.resume()
semaphore.wait(timeout: DispatchTime.distantFuture)
P.S. Why do I do it. Server returns limited count of data. To get more, I have to use offset.
This is deadlocking because you are waiting for a semaphore on the URLSession's delegateQueue. The default delegate queue is not the main queue, but it is a serial background queue (i.e. an OperationQueue with a maxConcurrentOperationCount of 1). So your code is waiting for a semaphore on the same serial queue that is supposed to be signaling the semaphore.
The tactical fix is to make sure you're not calling wait on the same serial queue that the session's completion handlers are running on. There are two obvious fixes:
Do not use shared session (whose delegateQueue is a serial queue), but rather instantiate your own URLSession and specify its delegateQueue to be a concurrent OperationQueue that you create:
let queue = OperationQueue()
queue.name = "com.domain.app.networkqueue"
let configuration = URLSessionConfiguration.default()
let session = URLSession(configuration: configuration, delegate: nil, delegateQueue: queue)
Alternatively, you can solve this by dispatching the code with the semaphore off to some other queue, e.g.
let mainRequest = session.dataTask(with: mainUrl) { data, response, error in
// ...
DispatchQueue.global(attributes: .qosUserInitiated).async {
let semaphore = DispatchSemaphore(value: 0)
for i in 1 ... n {
let childUrl = URL(string: "https://blabla/\(i)")!
let childRequest = session.dataTask(with: childUrl) { data, response, error in
// ...
semaphore.signal()
}
childRequest.resume()
_ = semaphore.wait(timeout: .distantFuture)
}
}
}
mainRequest.resume()
For the sake of completeness, I'll note that you probably shouldn't be using semaphores to issue these requests at all, because you'll end up paying a material performance penalty for issuing a series of consecutive requests (plus you're blocking a thread, which is generally discouraged).
The refactoring of this code to do that is a little more considerable. It basically entails issuing a series of concurrent requests, perhaps use "download" tasks rather than "data" tasks to minimize memory impact, and then when all of the requests are done, piece it all together as needed at the end (triggered by either a Operation "completion" operation or dispatch group notification).