Uploads Using Backgroundsessionconfiguration and Nsurlsessionuploadtask Cause App to Crash

Uploads using backgroundSessionConfiguration and NSURLSessionUploadTask cause app to crash

Okay, so this was kind of just me being foolish and not thorough here:

1) I'd set an exception breakpoint to get stack traces that was preventing me from see the actual exception error printout -- oops.

2) Can't use version of uploadTaskWithRequest that has a completion callback for a backgroundSessionConfiguration (not surprising but still not well documented).

3) Write your PNG data to /var/... and provide it to uploadTaskWithRequest with file:///var/... (this is just awkward because you don't often need to convert between the two for a single sequence of commands)

Happy to put up a NSUrlSessionUploadTask sample code here, since there seems to be zero of them on the entire interwebs. LMK if anyone wants that.

Unable to sustain a Constant Speed while Uploading files in the background with NSURLSession

From what I can tell, setTaskDidCompleteBlock: is not an Apple API, NSURLSession-associated method. It is an AFURLSessionManager method (docs). If you are using AFNetworking on this, then you need to be announcing that bold, top, front and center. That is not the same, at all, as using NSURLSession. I would guess AFNetworking's background NSURLSession-based implementation comes with its own foibles and idiosyncrasies.

For my part, whatever success I've had with sustained background NSURLSession uploads are using only the stock API.


Addressing questions, etc.

  • Regarding AFNetworking: we use it for general web api I/O. At the time NSURLSession came out, AFNetworking really didn't robustly support app-in-background ops, so I didn't use it. Perhaps because I went through the background NSURLSession pain & hazing, I look askance at AFNetworking backgrounding under the rubric of "Now you have two problems". But maybe they have cracked the nut by now.

    • I strive for one NSURLSession. I started out being cavalier about creation & destruction of sessions, but found this made for some truly gnarly problems. Experiences seem to vary on this.

    • I use the default HTTPMaximumConnectionsPerHost, no problems there. The Apple docs are silent on the default value, but here's what lldb tells me in the random particular device/OS I chose:


      (lldb) p [config HTTPMaximumConnectionsPerHost]
      (NSInteger) $0 = 4

      If you are having troubles with backgrounding slowing down, I doubt tweaking this is on the right track.

    • FWIW, background NSURLSessions do not support the block interfaces, delegate only.

Background upload multiple images using single NSURLSession uploadTaskWithRequest

To upload in a background session, the data must first saved to a file.

  1. Save the data to file using writeToFile:options:.
  2. Call NSURLSession uploadTaskWithRequest:fromFile: to create the task. Note that the request must not contain the data in the HTTPBody otherwise the upload will fail.
  3. Handle completion in the URLSession:didCompleteWithError: delegate method.

You may also want to handle uploads which complete while the app is in the background.

  1. Implement application:handleEventsForBackgroundURLSession:completionHandler in the AppDelegate.
  2. Create an NSURLSession with the provided identifier.
  3. Respond to the delegate methods as per a usual upload (e.g. handle the response in URLSession:didCompleteWithError:)
  4. Call URLSessionDidFinishEventsForBackgroundURLSession when you have completed processing the event.

To make this easier to manage, create one NSURLSession per upload task, each with a unique identifier.

Refer to the URL Session Programming Guide for implementation details.

Example AppDelegate:

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, NSURLSessionDelegate, NSURLSessionTaskDelegate {

var window: UIWindow?

typealias CompletionHandler = () -> Void

var completionHandlers = [String: CompletionHandler]()

var sessions = [String: NSURLSession]()

func upload(request: NSURLRequest, data: NSData)
{
// Create a unique identifier for the session.
let sessionIdentifier = NSUUID().UUIDString

let directoryURL = NSFileManager.defaultManager().URLsForDirectory(.CachesDirectory, inDomains: .UserDomainMask).first!
let fileURL = directoryURL.URLByAppendingPathComponent(sessionIdentifier)

// Write data to cache file.
data.writeToURL(fileURL, atomically: true);

let configuration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier(sessionIdentifier)

let session: NSURLSession = NSURLSession(
configuration:configuration,
delegate: self,
delegateQueue: NSOperationQueue.mainQueue()
)

// Store the session, so that we don't recreate it if app resumes from suspend.
sessions[sessionIdentifier] = session

let task = session.uploadTaskWithRequest(request, fromFile: fileURL)

task.resume()
}

// Called when the app becomes active, if an upload completed while the app was in the background.
func application(application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: CompletionHandler) {

let configuration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier(identifier)

if sessions[identifier] == nil {

let session = NSURLSession(
configuration: configuration,
delegate: self,
delegateQueue: NSOperationQueue.mainQueue()
)

sessions[identifier] = session
}

completionHandlers[identifier] = completionHandler
}

func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {

// Handle background session completion handlers.
if let identifier = session.configuration.identifier {

if let completionHandler = completionHandlers[identifier] {
completionHandler()
completionHandlers.removeValueForKey(identifier)
}

// Remove session
sessions.removeValueForKey(identifier)
}

// Upload completed.
}
}

To upload multiple images in a single request, the images must first be encoded into the multipart/formdata MIME type, as you have done. The difference being that this entire MIME message must be saved to a single file, which is the file that is uploaded to the server.

Here is an example which shows how to do this. It works by serialising the MIME parts directly to a file. You could also build up the message in an NSData, although you risk running into memory limitations when handling large files.

func uploadImages(request: NSURLRequest, images: [UIImage]) {

let uuid = NSUUID().UUIDString
let boundary = String(count: 24, repeatedValue: "-" as Character) + uuid

// Open the file
let directoryURL = NSFileManager.defaultManager().URLsForDirectory(.CachesDirectory, inDomains: .UserDomainMask).first!

let fileURL = directoryURL.URLByAppendingPathComponent(uuid)
let filePath = fileURL.path!

NSFileManager.defaultManager().createFileAtPath(filePath, contents: nil, attributes: nil)

let file = NSFileHandle(forWritingAtPath: filePath)!

// Write each image to a MIME part.
let newline = "\r\n"

for (i, image) in images.enumerate() {

let partName = "image-\(i)"
let partFilename = "\(partName).png"
let partMimeType = "image/png"
let partData = UIImagePNGRepresentation(image)

// Write boundary header
var header = ""
header += "--\(boundary)" + newline
header += "Content-Disposition: form-data; name=\"\(partName)\"; filename=\"\(partFilename)\"" + newline
header += "Content-Type: \(partMimeType)" + newline
header += newline

let headerData = header.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)

print("")
print("Writing header #\(i)")
print(header)

print("Writing data")
print("\(partData!.length) Bytes")

// Write data
file.writeData(headerData!)
file.writeData(partData!)
}

// Write boundary footer
var footer = ""
footer += newline
footer += "--\(boundary)--" + newline
footer += newline

print("")
print("Writing footer")
print(footer)

let footerData = footer.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
file.writeData(footerData!)

file.closeFile()

// Add the content type for the request to multipart.
let outputRequest = request.copy() as! NSMutableURLRequest

let contentType = "multipart/form-data; boundary=\(boundary)"
outputRequest.setValue(contentType, forHTTPHeaderField: "Content-Type")

// Start uploading files.
upload(outputRequest, fileURL: fileURL)
}

Making a POST request for a .ts file using NSURLSession in iOS

If you have access to the .ts file locally, you could use

[session uploadTaskRequest:request fromFile:[NSURL youUrlToTheTSFile]]

See the answers here: Uploads using backgroundSessionConfiguration and NSURLSessionUploadTask cause app to crash



Related Topics



Leave a reply



Submit