Parse Nested Completion Handlers

Implementing completion handlers for backgroundSession.uploadTask

If you're only interested in detecting the completion of the request, the simplest approach is to use a closure:

class CustomDelegate : NSObject, URLSessionDelegate, URLSessionTaskDelegate, URLSessionDataDelegate {

static var sharedInstance = CustomDelegate()
var uploadDidFinish: ((URLSessionTask, Error?) -> Void)?

func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
DispatchQueue.main.async {
uploadDidFinish?(task, error)
}
}

}

Then your view controller would set this closure before initiating the request, e.g.

CustomDelegate.sharedInstance.uploadDidFinish = { [weak self] task, error in
// update the UI for the completion here
}

// start the request here

If you want to update your UI for multiple situations (e.g. not only as uploads finish, but progress as the uploads are sent), you theoretically could set multiple closures (one for completion, one for progress), but often you'd adopt your own delegate-protocol pattern. (Personally, I'd rename CustomDelegate to something like UploadManager to avoid confusion about who's a delegate to what, but that's up to you.)

For example you might do:

protocol UploadDelegate: class {
func didComplete(session: URLSession, task: URLSessionTask, error: Error?)
func didSendBodyData(session: URLSession, task: URLSessionTask, bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64)
}

Then, in your network request manager (your CustomDelegate implementation), define a delegate property:

weak var delegate: UploadDelegate?

In the appropriate URLSession delegate methods, you'd call your custom delegate methods to pass along the information to the view controller:

func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
// do whatever you want here

DispatchQueue.main.async {
delegate?.didComplete(session: session, task: task, didCompleteWithError: error)
}
}

func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
// do whatever you want here

DispatchQueue.main.async {
delegate?.didSendBodyData(session: session, task: task, bytesSent: bytesSent, totalBytesSent: totalBytesSent, totalBytesExpectedToSend: totalBytesExpectedToSend)
}
}

Then, you'd declare your view controller to conform to your new protocol and implement these methods:

class ViewController: UIViewController, UploadDelegate {
...
func startRequests() {
CustomDelegate.sharedInstance.delegate = self

// initiate request(s)
}

func didComplete(session: URLSession, task: URLSessionTask, error: Error?) {
// update UI here
}

func didSendBodyData(session: URLSession, task: URLSessionTask, bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
// update UI here
}
}

Now, you might update this UploadDelegate protocol to capture model information and pass that as a parameter to your methods, too, but hopefully this illustrates the basic idea.


Some minor observations:

  1. When creating your session, you probably should excise the NSURL and NSMutableURLRequest types from your code, e.g.:

    let url = URL(string: "https://www.myurl.com")!
    var urlRequest = URLRequest(url: url)

    urlRequest.httpMethod = "POST"
    urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")

    let uploadTask = backgroundSession.uploadTask(with: urlRequest, fromFile: path)

    uploadTask.resume()
  2. You are looking for statusCode in didReceiveData. You really should be doing that in didReceiveResponse. Also, you generally get the status code from the URLResponse.

  3. You are parsing the response in didReceiveData. Generally, you should do that in didCompleteWithError (just in case it takes multiple calls to didReceiveData to receive the entire response).

  4. I don't know what this myObject.id is, but the identifier you've chosen, "com.example.myObject\(myObject.id)", is somewhat suspect:

    • Are you creating a new URLSession instance for each object? You probably want one for all of the requests.

    • When your app is suspended/jettisoned while the upload continues in the background, when the app is restarted, do you have a reliable way of reinstantiating the same session objects?
       

    Generally you'd want a single upload session for all of your uploads, and the name should be consistent. I'm not saying you can't do it the way you have, but it seems like it's going to be problematic recreating those sessions without going through some extra work. It's up to you.

    All of this is to say that I'd make sure you test your background uploading process works if the app is terminated and is restarted in background when the uploads finish. This feels like this is incomplete/fragile, but hopefully I'm just jumping to some incorrect conclusions and you've got this all working and simply didn't sharing some details (e.g. your app delegate's handleEventsForBackgroundURLSession) for the sake of brevity (which is much appreciated).

Parsing nested json response data using codable in iOS Swift?

Your struct is correct. See playground below for proof; I don't get an error. nil is very common result when you don't have the entire data set and you assume that some optional field in the response is non optional just because you see it in your sample data (your sample is not necessarily representative). Absent an actual spec from the server, you need to figure out what field is failing to decode and you may need to get a bunch of data to figure out which fields are really optional. You can do that by either putting in error handling code in your AF project above or just by pasting your response into my playground below. Either way the decoding error should tell you which field was not present.

import PlaygroundSupport
import UIKit
import WebKit

public struct TaskID: Decodable {

let embedded: Embedded?
let count: Int?

enum CodingKeys: String, CodingKey {
case count = "count"
case embedded = "_embedded"
}
}

public struct Embedded: Decodable {

let task: [Task]?
enum CodingKeys: String, CodingKey {
case task = "task"
}

}

public struct Task : Decodable {

let id : String?
let name: String?
let assignee: String?
let created: String?
let processDefinitionId: String?
enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
case assignee = "assignee"
case created = "created"
case processDefinitionId = "processDefinitionId"

}

}

let data = """
{
"_links": {
"self": {
"href": "/task"
}
},
"_embedded": {

"task": [
{
"_links": {
"assignee": {
"href": "/user/demo"
},
"execution": {
"href": "/execution/1b64cf75-0616-11ea-8860-120ef5ab2c25"
},
"identityLink": {
"href": "/task/1b64f688-0616-11ea-8860-120ef5ab2c25/identity-links"
},
"processDefinition": {
"href": "/process-definition/quickEvaluation:1:129ce2b1-0616-11ea-8860-120ef5ab2c25"
},
"processInstance": {
"href": "/process-instance/1b64cf75-0616-11ea-8860-120ef5ab2c25"
},
"self": {
"href": "/task/1b64f688-0616-11ea-8860-120ef5ab2c25"
}
},
"_embedded": {
"variable": []
},
"id": "1b64f688-0616-11ea-8860-120ef5ab2c25",
"name": "Quick Evaluation",
"assignee": "demo",
"created": "2019-11-13T13:04:20.687+0000",
"due": null,
"followUp": null,
"delegationState": null,
"description": null,
"executionId": "1b64cf75-0616-11ea-8860-120ef5ab2c25",
"owner": null,
"parentTaskId": null,
"priority": 50,
"processDefinitionId": "quickEvaluation:1:129ce2b1-0616-11ea-8860-120ef5ab2c25",
"processInstanceId": "1b64cf75-0616-11ea-8860-120ef5ab2c25",
"taskDefinitionKey": "QuickEvaluation",
"caseExecutionId": null,
"caseInstanceId": null,
"caseDefinitionId": null,
"suspended": false,
"formKey": "a8apps:suryoday:gng:v0.1.0:kycUpload",
"tenantId": null
},
{
"_links": {
"assignee": {
"href": "/user/demo"
},
"execution": {
"href": "/execution/412a03b7-06ae-11ea-8860-120ef5ab2c25"
},
"identityLink": {
"href": "/task/412a2aca-06ae-11ea-8860-120ef5ab2c25/identity-links"
},
"processDefinition": {
"href": "/process-definition/quickEvaluation:1:129ce2b1-0616-11ea-8860-120ef5ab2c25"
},
"processInstance": {
"href": "/process-instance/412a03b7-06ae-11ea-8860-120ef5ab2c25"
},
"self": {
"href": "/task/412a2aca-06ae-11ea-8860-120ef5ab2c25"
}
},
"_embedded": {
"variable": [
{
"_links": {
"self": {
"href": "/process-instance/412a03b7-06ae-11ea-8860-120ef5ab2c25/variables/loanAmount"
}
},
"_embedded": null,
"name": "loanAmount",
"value": "650000",
"type": "String",
"valueInfo": {}
},
{
"_links": {
"self": {
"href": "/process-instance/412a03b7-06ae-11ea-8860-120ef5ab2c25/variables/firstName"
}
},
"_embedded": null,
"name": "firstName",
"value": "Kamesh",
"type": "String",
"valueInfo": {}
}
]
},
"id": "412a2aca-06ae-11ea-8860-120ef5ab2c25",
"name": "Quick Evaluation",
"assignee": "demo",
"created": "2019-11-14T07:13:27.558+0000",
"due": null,
"followUp": null,
"delegationState": null,
"description": null,
"executionId": "412a03b7-06ae-11ea-8860-120ef5ab2c25",
"owner": null,
"parentTaskId": null,
"priority": 50,
"processDefinitionId": "quickEvaluation:1:129ce2b1-0616-11ea-8860-120ef5ab2c25",
"processInstanceId": "412a03b7-06ae-11ea-8860-120ef5ab2c25",
"taskDefinitionKey": "QuickEvaluation",
"caseExecutionId": null,
"caseInstanceId": null,
"caseDefinitionId": null,
"suspended": false,
"formKey": "a8apps:suryoday:gng:v0.1.0:kycUpload",
"tenantId": null
}

]
},
"count": 13
}
""".data(using: .utf8)!

let decoder = JSONDecoder()
do {
let decoded = try decoder.decode(TaskID.self, from: data)
print(decoded)
} catch ( let error ) {
print(error.localizedDescription)
}



Related Topics



Leave a reply



Submit