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:
When creating your session, you probably should excise the
NSURL
andNSMutableURLRequest
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()You are looking for
statusCode
indidReceiveData
. You really should be doing that indidReceiveResponse
. Also, you generally get the status code from theURLResponse
.You are parsing the response in
didReceiveData
. Generally, you should do that indidCompleteWithError
(just in case it takes multiple calls todidReceiveData
to receive the entire response).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
Decoding Different Type with and Without Array
How to Grab The Parent Object from a Subview
How to Implement Applescript Support in a Swift Macos App
Adding Drop Shadow to Table View Cell
How to Change an Inout Parameter from Within a Escaping Closure
Tintcolor Not Changing for UIbarbuttonitem for .Normal Stage in Case of iOS 13.2
Inmemory Realm Threading in Swift
How to Delay a Return-Statement in Swift
Why Does User Defaults Publisher Trigger Multiple Times
Swift Initialization Rule Confusion
How to Convert an Array to List in Realm
How to Calculate The Camera Position from Vuforia Gl Matrix
How to Pass Data from Using Post/Form Leaf Template
Nskeyedunarchiver Decodeobjectforkey: Cannot Decode Object of Class for Key (Ns.Objects)
Tvos Button Inside Navigationlink Is Not Working
Is There an Firebase Realtime Database Equivalent to The Increment Method Available in Firestore