I have a (custom, linked-list based) queue that I want to deserialize when the app starts and serialize when the app stops, like so (AppDelegate.swift):
func applicationWillResignActive(_ application: UIApplication) {
RequestManager.shared.serializeAndPersistQueue()
}
func applicationDidBecomeActive(_ application: UIApplication) {
RequestManager.shared.deserializeStoredQueue()
}
The issue is during serialization when I exit the app. Here's the code that's running:
public func serializeAndPersistQueue() {
do {
let encoder = JSONEncoder()
let data = try encoder.encode(queue) // Bad access here
if FileManager.default.fileExists(atPath: url.path) {
try FileManager.default.removeItem(at: url)
}
FileManager.default.createFile(atPath: url.path, contents: data, attributes: nil)
}
catch {
print(error)
}
}
As you can see, fairly straightforward. It uses the JSONEncoder to convert my queue to a data object, then writes that data to the file at url.
However, during encoder.encode() I get EXC_BAD_ACCESS every time, without fail.
Additionally, I should note that peak and dequeue operations are conducted on the queue from a background thread. I'm not sure if that makes a difference due to my lack of understanding surrounding GCD. Here's what that method looks like:
private func processRequests() {
DispatchQueue.global(qos: .background).async { [unowned self] in
let group = DispatchGroup()
let semaphore = DispatchSemaphore(value: 0)
while !self.queue.isEmpty {
group.enter()
let request = self.queue.peek()!
self.sendRequest(request: request, completion: { [weak self] in
_ = self?.queue.dequeue()
semaphore.signal()
group.leave()
})
semaphore.wait()
}
group.notify(queue: .global(), execute: { [weak self] in
print("Ending the group")
})
}
}
Lastly, I'll note that:
My queue conforms to the Codable protocol just fine––well, there are no compiler errors, at least. If its implementation beyond that matters, let me know and I'll show it.
The crash occurs a few seconds after I exit the app, while the execution of the processRequests function stops immediately after
Related
I perform the next code
let task = session.uploadTask(with: request, from: requestData.body) { data, response, error in
if let error = error {
DispatchQueue.main.async {
completion(.failure(error))
}
}
}
All is clear, after doing network request in background I return completion to .main . But how to handle case, if I want to call completion not in .main but in thread in which session.uploadTask was initiated, because in my application it could be not only .main .
There is not a GCD mechanism to retrieve the current dispatch queue so that you can later dispatch to it. (A long time ago, there used to be a way to fetch the current queue, but it was deprecated back in iOS 7, and even then it was “Recommended for debugging and logging purposes only.”)
If you want to call the completion handlers on specific dispatch queue, I would suggest supplying an explicit DispatchQueue parameter to the method. Below, I have it default to .main, but the caller can override that with whatever it wants:
func perform(_ request: URLRequest, with data: Data, on queue: DispatchQueue = .main, completion: #escaping (Result<T, Error>) -> Void) {
let task = session.uploadTask(with: request, from: data) { data, response, error in
if let error = error {
queue.async {
completion(.failure(error))
}
}
...
}
...
}
I know that this is not precisely what you are looking for, but it is an easy way to let the caller specify on which queue your closure will be called.
If you are using operation queues, you can get the current to determine the current operation queue. And should one do this, one would use addOperation on that operation queue in order to call the completion handler.
func perform(_ request: URLRequest, with data: Data, completion: #escaping (Result<T, Error>) -> Void) {
guard let queue = OperationQueue.current else {
fatalError("Must be called from operation queue")
}
let task = session.uploadTask(with: request, from: data) { data, response, error in
if let error = error {
queue.addOperation {
completion(.failure(error))
}
}
...
}
...
}
But this pattern only works if the caller was using operation queue, not when only using dispatch queues. For this reason, I would still be inclined to adopt the pattern of supplying the target operation queue as a parameter:
func perform(_ request: URLRequest, with data: Data, on queue: OperationQueue = .main, completion: #escaping (Result<T, Error>) -> Void) {
let task = session.uploadTask(with: request, from: data) { data, response, error in
if let error = error {
queue.addOperation {
completion(.failure(error))
}
}
...
}
...
}
If you would like to call completion on the current thread simply call completion().
current thread
completion(.failure(error))
main thread
DispatchQueue.main.async {
completion(.failure(error))
}
background thread
Dispatch.global(qos: .background) {
completion(.failure(error))
}
I used this solution:
guard let currentDispatch = OperationQueue.current?.underlyingQueue else {
return
}
let task = session.uploadTask(with: request, from: requestData.body) { data, response, error in
if let error = error {
currentDispatch.async {
completion(.failure(error))
}
}
}
Have not tested it too much, but quick debugging shows what I wanted to achieve,
Background
The function below calls two functions, which both access an API, retrieve JSON data, parse through it, etc, and then take that data and populates the values of an object variable in my View Controller class.
func requestWordFromOxfordAPI(word: String, completion: (_ success: Bool) -> Void) {
oxfordAPIManager.fetchDictData(word: word)
oxfordAPIManager.fetchThesData(word: word)
completion(true)
}
Normally, if there was only one function fetching data, and I wanted to call a new function that takes in the data results and does something with them, I would use a delegate method and call it within the closure of the data fetching function.
For Example:
Here, I fetch data from my firebase database and if retrieving the data is succesful, I call self.delegate?.populateWordDataFromFB(result: combinedModel). Since closures occur on separate thread, this ensures that the populateWordDataFromFB function runs only once retrieving the data has finished. Please correct me if I am wrong. I have just recently learned this and am still trying to see the whole picture.
func readData(word: String) {
let docRef = db.collection(K.FBConstants.dictionaryCollectionName).document(word)
docRef.getDocument { (document, error) in
let result = Result {
try document.flatMap {
try $0.data(as: CombinedModel.self)
}
}
switch result {
case .success(let combinedModel):
if let combinedModel = combinedModel {
self.delegate?.populateWordDataFromFB(result: combinedModel)
} else {
self.delegate?.fbDidFailWithError(error: nil, summary: "\(word) not found, requesting from OxfordAPI")
self.delegate?.requestWordFromOxfordAPI(word: word, completion: { (success) in
if success {
self.delegate?.populateWordDataFromOX()
} else {print("error with completion handler")}
})
}
case .failure(let error):
self.delegate?.fbDidFailWithError(error: error, summary: "Error decoding CombinedModel")
}
}
}
Also notice from the above code that if the data is not in firebase, I call the delegate method below, which is where I am running into my issue.
self.delegate?.requestWordFromOxfordAPI(word: word, completion: { (success) in
if success {
self.delegate?.populateWordDataFromOX()
} else {print("error with completion handler")}
})
My Issue
What I am struggling with is the fact that the oxfordAPIManager.fetchDictData(word: word) and oxfordAPIManager.fetchThesData(word: word) functions both have closures.
The body of these functions look like this:
if let url = URL(string: urlString) {
var request = URLRequest(url: url)
request.addValue(K.APISettings.acceptField, forHTTPHeaderField: "Accept")
request.addValue(K.APISettings.paidAppID , forHTTPHeaderField: "app_id")
request.addValue(K.APISettings.paidAppKey, forHTTPHeaderField: "app_key")
let session = URLSession.shared
_ = session.dataTask(with:request) { (data, response, error) in
if error != nil {
self.delegate?.apiDidFailWithError(error: error, summary: "Error performing task:")
return
}
if let safeData = data {
if let thesaurusModel = self.parseThesJSON(safeData) {
self.delegate?.populateThesData(thesModel: thesaurusModel, word: word)
}
}
}
.resume()
} else {print("Error creating thesaurus request")}
I assume both of these functions are running on separate threads in the background. My goal is to call another function once both the oxfordAPIManager.fetchDictData(word: word) and oxfordAPIManager.fetchThesData(word: word) functions run. These two functions will populate the values of an object variable in my view controller which I will use in the new function. I don't want the new function to be called before the object variable in the view controller is populated with the right data so I tried to implement a completion handler. The completion handler function is being called BEFORE the two functions terminate, so when the new function tries to access the object variable in the View Controller, it's empty.
This is my first time trying to implement a completion handler and I tried to follow some other stack overflow posts but was unsuccessful. Also if this is the wrong approach let me know too, please. Sorry for the long explanation and thank you for any input.
Use DispatchGroup for this,
Example:
Create a DispatchGroup,
let group = DispatchGroup()
Modify the requestWordFromOxfordAPI(word: completion:) method to,
func requestWordFromOxfordAPI(word: String, completion: #escaping (_ success: Bool) -> Void) {
fetchDictData(word: "")
fetchThesData(word: "")
group.notify(queue: .main) {
//code after both methods are executed
print("Both methods executed")
completion(true)
}
}
Call enter() and leave() methods of DispatchGroup at the relevant places in fetchDictData(word:) and fetchThesData(word:) methods.
func fetchDictData(word: String) {
group.enter()
URLSession.shared.dataTask(with: url) { (data, response, error) in
//your code
group.leave()
}.resume()
}
func fetchThesData(word: String) {
group.enter()
URLSession.shared.dataTask(with: url) { (data, response, error) in
//your code
group.leave()
}.resume()
}
At last call requestWordFromOxfordAPI(word: completion:)
requestWordFromOxfordAPI(word: "") { (success) in
print(success)
}
I have 10 urls in an array and when 4 of them downloaded I need to display them. Im using Semaphores and groups to implement . But looks like im hitting deadlock. Not sure how to proceed. Please advice how I can
Simulating same in playground:
PlaygroundPage.current.needsIndefiniteExecution = true
let group = DispatchGroup()
let queue = DispatchQueue.global(qos: .userInteractive)
let semaphore = DispatchSemaphore(value: 4)
var nums: [Int] = []
for i in 1...10 {
group.enter()
semaphore.wait()
queue.async(group: group) {
print("Downloading image \(i)")
// Simulate a network wait
Thread.sleep(forTimeInterval: 3)
nums.append(i)
print("Hola image \(i)")
if nums.count == 4 {
print("4 downloaded")
semaphore.signal()
group.leave()
}
}
if nums.count == 4 {
break
}
}
group.notify(queue: DispatchQueue.main) {
print(nums)
}
I get this in o/p console
> Downloading image 1
> Downloading image 2
> Downloading image 3
> Downloading image 4
Semaphores(41269,0x70000ade5000) malloc: *** error for object 0x1077d4750: pointer being freed was not allocated
Semaphores(41269,0x70000ade5000) malloc: *** set a breakpoint in malloc_error_break to debug
I'm expecting to print [1,2,3,4] in order
I know im trying to access a shared resource in async but not sure how I can fix this. Please advice
Also How can I use this with semaphore's if I want to download 4,4,2 tasks at a time so it display [1,2,3,4,5,6,7,8,9,10] in my ouput
Your title says “Downloading Images in order of url’s”, but your code snippet is not attempting to do that. It appears to be attempting to use semaphores to constrain the download to four images at a time, but it won’t guarantee that they’ll be in order.
It is commendable that this code snippet isn’t attempting to download them in order, sequentially, one after another, because that would impose a huge performance penalty. It is also good that this code snippet is constraining this degree of concurrency to something reasonable, thereby avoiding exhausting worker threads or causing some of the latter requests to timeout. So, the idea of using semaphore to allow concurrent image download, but constrain it to four at a time, is a fine approach; we only need to sort the results at the end if you want them in order.
But before we get to that, let’s tackle a bunch of problems in the supplied code snippet:
You are calling group.enter() and semaphore.wait() for every iteration (which is correct), but group.leave() and semaphore.signal() only when i is 4 (which is not correct). You want to leave and signal for every iteration.
Obviously, that break call is not needed, either.
So, to fix this “do four at a time” process, one can simplify this code:
let group = DispatchGroup()
let queue = DispatchQueue.global(qos: .userInteractive)
let semaphore = DispatchSemaphore(value: 4)
var nums: [Int] = []
for i in 1...10 {
group.enter()
semaphore.wait()
queue.async() { // NB: the `group` parameter is not needed
print("Downloading image \(i)")
// Simulate a network wait
Thread.sleep(forTimeInterval: 3)
nums.append(i)
print("Hola image \(i)")
semaphore.signal()
group.leave()
}
}
group.notify(queue: .main) {
print(nums)
}
That will download four images at a time and will call your group.notify closure when they’re all done.
While the above fixes the semaphore and group logic, there is yet another problem lurking in the above code snippet. It is updating that nums array from multiple background threads, but Array is not thread-safe. So you should synchronize those updates to that array. An easy way to achieve this is to dispatch that update back to the main thread. (Any serial queue would have been fine, but the main thread works fine for this purpose.)
Also, since one should never call wait on the main queue, so I’d suggest that you explicitly dispatch this entire for loop to a background thread:
DispatchQueue.global(qos: .utility).async {
let group = DispatchGroup()
let queue = DispatchQueue.global(qos: .userInteractive)
let semaphore = DispatchSemaphore(value: 4)
var nums: [Int] = []
for i in 1...10 {
group.enter()
semaphore.wait()
queue.async() {
print("Downloading image \(i)")
// Simulate a network wait
Thread.sleep(forTimeInterval: 3)
DispatchQueue.main.async {
nums.append(i)
print("Hola image \(i)")
}
semaphore.signal()
group.leave()
}
}
group.notify(queue: .main) {
print(nums)
}
}
That is now the correct “do four at a time and let me know when it’s done.”
OK, now that we’re downloading all of the images properly, let’s figure out how to sort the results. Frankly, I think it’s easier to follow what’s going on if we imagine that we have some image download method, like so, that downloads a particular image:
func download(_ url: URL, completion: #escaping (Result<UIImage, Error>) -> Void) { ... }
Then the routine to (a) download the images, no more than four at a time; and (b) return the results back in order, might look like:
func downloadAllImages(_ urls: [URL], completion: #escaping ([UIImage]) -> Void) {
DispatchQueue.global(qos: .utility).async {
let group = DispatchGroup()
let semaphore = DispatchSemaphore(value: 4)
var imageDictionary: [URL: UIImage] = [:]
// download the images
for url in urls {
group.enter()
semaphore.wait()
self.download(url) { result in
defer {
semaphore.signal()
group.leave()
}
switch result {
case .failure(let error):
print(error)
case .success(let image):
DispatchQueue.main.async {
imageDictionary[url] = image
}
}
}
}
// now sort the results
group.notify(queue: .main) {
completion(urls.compactMap { imageDictionary[$0] })
}
}
}
And you’d call it like so:
downloadAllImages(urls) { images in
self.images = images
self.updateUI() // do whatever you want to trigger the update of the UI
}
FWIW, the “download single image” routine might look like:
enum DownloadError: Error {
case notImage
case invalidStatusCode(URLResponse)
}
func download(_ url: URL, completion: #escaping (Result<UIImage, Error>) -> Void) {
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, let response = response as? HTTPURLResponse, error == nil else {
completion(.failure(error!))
return
}
guard 200..<300 ~= response.statusCode else {
completion(.failure(DownloadError.invalidStatusCode(response)))
return
}
guard let image = UIImage(data: data) else {
completion(.failure(DownloadError.notImage))
return
}
completion(.success(image))
}
}
And this is using the Swift 5 Result enumeration. If you’re using an earlier version of Swift, you can define a simple rendition of this enum yourself:
enum Result<Success, Failure> {
case success(Success)
case failure(Failure)
}
Finally, it’s worth noting a few other alternatives:
Wrap your network request in asynchronous Operation subclass and add them to an operation queue whose maxConcurrentOperationCount is set to 4. If you’re interested in this approach, I can supply some references.
Use an image downloading library like Kingfisher.
Instead of manual downloading of all the images, use the UIImageView extension (such as provided by Kingfisher) and completely abandon the “download all images” process at all, and move to a pattern where you simply instruct your image views to asynchronously retrieve the images in either a just-in-time manner (or prefetching).
I am trying to call 3 functions in order but each function needs to have been completed before the next should run. Each function has a completion handler that calls another function upon completion. After reading lots online about dispatch queues I though this may be the best way to approach it, that's if I am understanding it correctly of course. When I run my code Each function is called in order but not when the previous has been completed. In the first function I am downloading an image from firebase but the second function gets called before the image has downloaded. I've taken out specifics in my code but this is what I have so far.
typealias COMPLETION = () -> ()
let functionOne_completion = {
print("functionOne COMPLETED")
}
let functionTwo_completion = {
print("functionTwo COMPLETED")
}
let functionThree_completion = {
print("functionThree COMPLETED")
}
override func viewDidLoad() {
super.viewDidLoad()
let queue = DispatchQueue(label: "com.myApp.myQueue")
queue.sync {
functionOne(completion: functionOne_completion)
functionTwo(completion: functionTwo_completion)
functionThree(completion: functionThree_completion)
}
func functionOne(completion: #escaping COMPLETION) {
print("functionOne STARTED")
completion()
}
func functionTwo(completion: #escaping COMPLETION) {
print("functionTwo STARTED")
completion()
}
func functionThree(completion: #escaping COMPLETION) {
print("functionThree STARTED")
completion()
}
You could use DispatchGroup
DispatchQueue.global().async {
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
functionOne { dispatchGroup.leave() }
dispatchGroup.wait() //Add reasonable timeout
dispatchGroup.enter()
functionTwo { dispatchGroup.leave() }
dispatchGroup.wait()
dispatchGroup.enter()
functionThree { dispatchGroup.leave() }
dispatchGroup.wait()
dispatchGroup.notify(queue: .main) {
//All tasks are completed
}
}
You need to call the second function on the completion of the first.
Something like:
func first(_ completion : #escaping()->()){
print("first")
completion()
}
func second(_ completion : #escaping()->()){
print("second")
}
func third(){
print("third")
}
override func viewDidLoad(){
....
first{
self.second{
self.third()
}
}
}
So when your image download gets finished, inside the completion block where you get the callback of download completion, you should call your second method/block passed as argument which in turn will call your second method.
Excuse me if this a noobish question but I don't know the difference between executing a block of code after an API request is received and parsed via GCD, delegates and closures.
As far as I know, a creating a session to download data from an API URL is done on the main thread unless I execute the code inside a a GCD block or a delegate or a closure.
Here are two examples:
Using GCD
DispatchQueue.global(qos: .utility).async {
let requestURL = URL(string: "http://echo.jsontest.com/key/value/one/two")
let session = URLSession.shared
let task = session.dataTask(with: requestURL!) {
(data, response, error) in
print(data as Any)
DispatchQueue.main.async {
print("Hello")
}
}
task.resume()
}
Using Delegate:
import Foundation
import UIKit
protocol WeatherDataDownloaderProtocol {
func setData(weatherData: WeatherData)
}
class WeatherDataDownloader {
var weatherData = WeatherData()
var delegate: WeatherDataDownloaderProtocol?
func downloadWeatherData() {
let API_URL = WEATHER_FORECAST_URL
guard let URL = URL(string: API_URL) else {
print("Error: No valid URL")
return
}
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
let task = session.dataTask(with: URL) { (data, response, error) in
guard error == nil else {
print("Error getting data")
print("\(error)")
return
}
guard let responseData = data else {
print("Error: Did not receive data")
return
}
do {
guard let JSON = try JSONSerialization.jsonObject(with: responseData, options: []) as? Dictionary<String, AnyObject> else {
print("Error: Error trying to convert data to JSON")
return
}
print(JSON)
self.sendDataBack()
} catch {
print("Error: Parsing JSON data error")
return
}
}
task.resume()
}
func sendDataBack() {
if let _delegate = delegate {
_delegate.setData(weatherData: weatherData)
}
}
}
Both, print("Hello") and print(JSON) + self.sendDataBack() will execute after the JSON is retrieved and parsed. What's the difference between both methods? Does it have anything to do with whether my app would crash if I navigate out of the viewController while waiting for the network response?
Thanks a lot
In your first approach, the .async call is not necessary. URLSession dataTask is a background task.
So the choice is not GDC vs. delegates but completion handler vs. delegate.
Opinion based:
Using a delegate is more work and harder to read because you have to check in other areas of the code if the delegate is actually set and who it is and what it actually does.
Also no code might be executed in case the delegate does not exist any more at the time your network call has finished. So for this case I plead for using a completion closure.
Both are correct. The block/closure approach is newer and considered to have better readability since you don't have to jump between functions and even between files to follow the course of your code.
DispatchQueue.global(qos: .utility).async {
let requestURL = URL(string: "http://echo.jsontest.com/key/value/one/two")
let session = URLSession.shared
let task = session.dataTask(with: requestURL!) {
(data, response, error) in
print(data as Any)
DispatchQueue.main.async {
print("Hello")
}
}
task.resume()
}
In this method your service hits in background thread and when you completed your in background thread you come back in main thread using this method
DispatchQueue.main.async {
print("Hello")
}
and then your print("Hello") will call in main thread.
While the method
downloadWeatherData
defined in appdelegate also hits the service in background thread but in the manner of closure because closure also works like a background thread. Using closure when your task completes your control automatically comes back in main thread where you call print(JSON).
Now comes to your problem, the best thing is that you should wait untill your task complete and you get the json response on your viewcontroller then move to your next controller other your app may crash in some situations.