Completion Handler Causes Deinit To Not Be Called - swift

I have a network task that contains a completion handler, allowing me to determine if and when the task completed successfully;
func performUpload(url: URL, completionHandler: #escaping (_ response: Bool) -> ()) {
photoSave.savePhotoRemote(assetURL: url) { response in
completionHandler(response)
}
}
I call this function from another UIView, by using the following;
UploadHelper.shared.performUpload(url: fileAssetPath) { response in
if response {
// do stuff
}
}
What I am noticing is that when I capture the response and do stuff, the class that called this function will never deinit. However, if I change my function to the following;
UploadHelper.shared.performUpload(url: fileAssetPath) { response in }
and don't do anything with the response, the class will deinit.
What am I doing wrong with my function? I would like to capture the response accordingly, but not at the expense of my class not being released from memory.

You've got a retain cycle. Break it. Change
UploadHelper.shared.performUpload(url: fileAssetPath) { response in
if response {
// do stuff
}
}
to
UploadHelper.shared.performUpload(url: fileAssetPath) { [unowned self] response in
if response {
// do stuff
}
}

Related

Swift: Recursive async func with completion handler that does not get called

I need to fetch large amounts of data from an endpoint in an async way. The API endpoint serves a predefined amount of data at a time. After the first request I must check to see if I get a "next" url from the response and visit that link in order to continue the download. This recursive behaviour continues until all available data has been served, in other words paging functionality (HAL links). At this point I have implemented a func that download recursively, however: problem is that the final completion handler does not seem to get called.
Demo code: The ThingsApi is a class that encapsulates the actual API call. The important thing is that this class has an initial url and during recursion will get specific url's to visit asynchronously. I call the downloadThings() func and need to get notified when it is finished. It works if I leave recursion out of the equation. But when recursion is in play then nothing!
I have created a simplified version of the code that illustrate the logic and can be pasted directly into the Playground. The currentPage and pages var's are just there to demo the flow. The last print() statement does not get called. Leave the currentPage += 1 to experience the problem and set currentPage += 6 to avoid recursion. Clearly I am missing out of some fundamental concept here. Anyone?
import UIKit
let pages = 5
var currentPage = 0
class ThingsApi {
var url: URL?
var next: URL?
init(from url: URL) {
self.url = url
}
init() {
self.url = URL(string: "https://whatever.org")
}
func get(completion: #escaping (Data?, HTTPURLResponse?, Error?) -> Void) {
// *** Greatly simplified
// Essentially: use URLSession.shared.dataTask and download data async.
// When done, call the completion handler.
// Simulate that the download will take 1 second.
sleep(1)
completion(nil, nil, nil)
}
}
func downloadThings(url: URL? = nil, completion: #escaping (Bool, Error?, String?) -> Void) {
var thingsApi: ThingsApi
if let url = url {
// The ThingsApi will use the next url (retrieved from previous call).
thingsApi = ThingsApi(from: url)
} else {
// The ThingsApi will use the default url.
thingsApi = ThingsApi()
}
thingsApi.get(completion: { (data, response, error) in
if let error = error {
completion(false, error, "We have nothing")
} else {
// *** Greatly simplified
// Parse the data and save to db.
// Simulate that the thingsApi.next will have a value 5 times.
currentPage += 1
if currentPage <= pages {
thingsApi.next = URL(string: "https://whatever.org?page=\(currentPage)")
}
if let next = thingsApi.next {
// Continue downloading things recursivly.
downloadThings(url: next) { (success, error, feedback) in
guard success else {
completion(false, error, "failed")
return
}
}
} else {
print("We are done")
completion(true, nil, "done")
print("I am sure of it")
}
}
})
}
downloadThings { (success, error, feedback) in
guard success else {
print("downloadThings() failed")
return
}
// THIS DOES NOT GET EXECUTED!
print("All your things have been downloaded")
}
It seems like this is simply a case of "you forgot to call it yourself" :)
In this if statement right here:
if let next = thingsApi.next {
// Continue downloading things recursivly.
downloadThings(url: next) { (success, error, feedback) in
guard success else {
completion(false, error, "failed")
return
}
}
} else {
print("We are done")
completion(true, nil, "done")
print("I am sure of it")
}
Think about what happens on the outermost call to downloadThings, and execution goes into the if branch, and the download is successful. completion is never called!
You should call completion after the guard statement!

Swift: How can I use synchronized thread for Alamofire?

I want to use synchronized thread using Alamofire.
I am using like this code for synchronization.
let queue1 = DispatchQueue(label: "queue1")
let queue2 = DispatchQueue(label: "queue2")
queue1.async {
self.dataFromServer.getData()
}
queue2.async {
//check if success to get the data from server
while(!self.dataFromServer.resultData){}
DispatchQueue.main.async {
// do something on screen
}
}
...
func getData() {
Alamofire.request(url).responseJSON { response in
if response.error == nil {
// get data
self.resultData = true
}else{
// error
}
}
}
I want to do something after get data from server using Alamofire.
Is this correct?
If this is not good, please tell me about the synchronization please.
Kind regards.
There is another and probably the good way of doing it by using closures.
// You don't need to create queue and it asynchronously, since Alamofire calls are async.
// You can remove queue statement
let queue1 = DispatchQueue(label: "queue1")
queue1.async {
self.dataFromServer.getData(completionClosure: { (resultData) in
if resultData {
}
else {
}
})
}
Then in your function have a parameter that takes a closure and call it with success or failure boolean.
func getData(completionClosure: (Bool) -> Void) {
Alamofire.request(url).responseJSON { response in
if response.error == nil {
// get data
completionClosure(true)
}
else{
completionClosure(false)
}
}
}
you can call
dispatch_sync
instead of async or you can call other API in the success block of first API
Modify the getData() function to return block so that you get callback on the sucess of the api call
func getData(sucess:((Void) -> Void)?) {
Alamofire.request(url).responseJSON { response in
if response.error == nil {
// get data
sucess!()
self.resultData = true
}else{
// error
}
}
}
Do whatever you want to do after the api response has received in the sucess callback of the getData() function

Swift loop closure function, then completion [duplicate]

I have a scenario where I want to perform three distinct asynchronous tasks in parallel. Once all three tasks are complete, I then want the calling method to be aware of this and to call its own completion handler.
Below is a very simplified version of the logic for this:
class ViewController: UIViewController {
func doTasks(with object: Object, completionHandler: () -> Void) {
// Once A, B & C are done, then perform a task
wrapupTask()
// When task is complete, call completionHandler
completionHandler()
}
}
fileprivate extension ViewController {
func taskA(with object: Object, completionHandler: () -> Void) {
// Do something
completionHandler()
}
func taskB(with object: Object, completionHandler: () -> Void) {
// Do something
completionHandler()
}
func taskC(with object: Object, completionHandler: () -> Void) {
// Do something
completionHandler()
}
}
I could easily chain the handlers together, but then the task will likely take longer and the code will suck.
Another item I considered was a simple counter that incremented each time a task completed, and then once it hit 3, would then call the wrapupTask() via something like this:
var count: Int {
didSet {
if count == 3 {
wrapupTask()
}
}
}
Another option I have considered is to create an operation queue, and to then load the tasks into it, with a dependency for when to run my wrap up task. Once the queue is empty, it will then call the completion handler. However, this seems like more work than I'd prefer for what I want to accomplish.
My hope is that there is something better that I am just missing.
Just to pick up on what OOPer said, you are looking for DispatchGroup. In the following, the calls to taskA, taskB, and taskC are pseudo-code, but everything else is real:
func doTasks(with object: Object, completionHandler: () -> Void) {
let group = DispatchGroup()
group.enter()
taskA() {
// completion handler
group.leave()
}
group.enter()
taskB() {
// completion handler
group.leave()
}
group.enter()
taskC() {
// completion handler
group.leave()
}
group.notify(queue: DispatchQueue.main) {
// this won't happen until all three tasks have finished their completion handlers
completionHandler()
}
}
Every enter is matched by a leave at the end of the asynchronous completion handler, and only when all the matches have actually executed do we proceed to the notify completion handler.

Do something when all alamofire requests are finished

I got an array of url and I want to make an alamofire request for each url in that array:
func getData(completion: #scaping(_success: Bool) -> Void) {
for url in self.myArray {
Alamofire.request(url).responseImage { response in
if let image = response.result.value {
print(image)
completion(true)
}
}
}
}
The problem is I can't know when ALL requests are finished, maybe because the for loop. Even using the completion handler.
If I try to do something on getData success, some requests are not finished yet.
I'd like to do something after all requests are done, like update a tableView
if you know how many urls you have (which you do) just keep a count of how often you reach the completion handler - something like this.
func getData(completion: #scaping(_success: Bool) -> Void) {
var urlCounter = self.myArray.count
for url in self.myArray {
Alamofire.request(url).responseImage { response in
if let image = response.result.value {
print(image)
completion(true)
urlCounter -= 1
if urlCounter == 0
{
// everything finished - do completion stuff
}
}
}
}
}

NSLock.lock() executed while lock already held?

I'm reviewing some Alamofire sample Retrier code:
func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: #escaping RequestRetryCompletion) {
lock.lock() ; defer { lock.unlock() }
if let response = request.task.response as? HTTPURLResponse, response.statusCode == 401 {
requestsToRetry.append(completion)
if !isRefreshing {
refreshTokens { [weak self] succeeded, accessToken, refreshToken in
guard let strongSelf = self else { return }
strongSelf.lock.lock() ; defer { strongSelf.lock.unlock() }
...
}
}
} else {
completion(false, 0.0)
}
}
I don't follow how you can have lock.lock() on the first line of the function and then also have that same line strongSelf.lock.lock() within the closure passed to refreshTokens.
If the first lock is not released until the end of the should method when the defer unlock is executed then how does the the second strongSelf.lock.lock() successfully execute while the first lock is held?
The trailing closure of refreshTokens, where this second call to lock()/unlock() is called, runs asynchronously. This is because the closure is #escaping and is called from within a responseJSON inside the refreshTokens routine. So the should method will have performed its deferred unlock by the time the closure of refreshTokens is actually called.
Having said that, this isn't the most elegant code that I've seen, where the utility of the lock is unclear and the risk of deadlocking is so dependent upon the implementation details of other routines. It looks like it's OK here, but I don't blame you for raising an eyebrow at it.