How to use session.uploadTask in background i am getting crash - ios

I am getting crash
it says *** Terminating app due to uncaught exception 'NSGenericException', reason: 'Completion handler blocks are not supported in background sessions. Use a delegate instead.'
var Boundary = "\(boundary.generateBoundaryString())_boundary"
private lazy var session: URLSession = {
let config = URLSessionConfiguration.background(withIdentifier: "MyUniqeId")
config.isDiscretionary = true
config.sessionSendsLaunchEvents = true
return URLSession(configuration: config, delegate: self, delegateQueue: nil)
}()
func webServiceForUploadImages(urlStr:String,params:[String:String],fileUrl:String,imageData:Data,success :#escaping (AppMedia) -> Void ,failure:#escaping (NSError) -> Void) -> Void{
let url = Constant.BASE_URL + urlStr
print(url)
if(reachAbility.connection != .none){
var request = URLRequest(url: URL(string: url)!)
request.httpMethod = "POST"
request.allHTTPHeaderFields = Header.headers()
request.setValue("multipart/form-data; boundary=\(Boundary)", forHTTPHeaderField: "Content-Type")
let data = try! createBody(with: params, filePathKey: "file", paths: [fileUrl], boundary: "\(Boundary)", imageData: imageData)
session.uploadTask(with: request, from: data) { (data, response, err) in
if response != nil{
guard let response = response as? HTTPURLResponse else {return}
handleError.shared.HandleReponseTokenExpireError(dataResponse: response, success: { (response) in
})
if(err != nil){
print("\(err!.localizedDescription)")
}
guard let responseData = data else {
print("no response data")
return
}
if let responseString = String(data: responseData, encoding: .utf8) {
DispatchQueue.main.async {
let dict = Utility.jsonToDict(str: responseString)
let mediaDict = AppMedia(fromDictionary: dict as! [String : Any])
Constant.KAppDelegate.hideProgressHUD()
success(mediaDict)
}
// print("uploaded to: \(responseString)")
}
}else{
DispatchQueue.main.async {
failure(err! as NSError)
Constant.KAppDelegate.hideProgressHUD()
Constant.KAppDelegate.showErrorMessage(title: "Error", message: Constant.ServerError, msgType: .error)
}
}
}.resume()
}else{
self.showErrorMsg(str: Constant.ConnectivityError)
}
}
let config = URLSessionConfiguration.background(withIdentifier: "MyUniqeId")
using this gives me crash

To upload using background URLSessionConfiguration there are a few special considerations:
Cannot use completion handlers (because the app might not be running when you finish the upload). You must use delegate-base methods, e.g. uploadTask(with:fromFile:).
For example:
func startUpload(for request: URLRequest, from data: Data) throws -> URLSessionUploadTask {
let fileURL = URL(fileURLWithPath: NSTemporaryDirectory())
.appendingPathComponent(UUID().uuidString)
try data.write(to: fileURL)
let task = session.uploadTask(with: request, fromFile: fileURL)
task.resume()
return task
}
That obviously assumes that you’ve specified your delegate and implemented the appropriate delegate methods:
extension BackgroundSession: URLSessionDelegate {
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
DispatchQueue.main.async {
self.savedCompletionHandler?()
self.savedCompletionHandler = nil
}
}
}
extension BackgroundSession: URLSessionTaskDelegate {
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error = error {
print(error)
return
}
print("success")
}
}
Note, we cannot use uploadTask(with:from:) because that’s using Data for that second parameter, which is not allowed for background sessions. Instead one must save the body of the request into a file and then use uploadTask(with:fromFile:).
Remember to handle the scenario where the upload finishes when your app is not running. Namely, the app delegate’s handleEventsForBackgroundURLSession must capture the completion handler. For example, I’ll have a property in my BackgroundSession to save the completion handler:
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: #escaping () -> Void) {
BackgroundSession.shared.savedCompletionHandler = completionHandler
}
And then you want to implement urlSessionDidFinishEvents(forBackgroundURLSession:) and call the saved completion handler:
extension BackgroundSession: URLSessionDelegate {
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
DispatchQueue.main.async {
self.savedCompletionHandler?()
self.savedCompletionHandler = nil
}
}
}
By the way, Downloading Files in the Background discusses many of these considerations (e.g. delegate API rather than closure based API, app delegate issues, etc.). It even discusses the requirements that upload tasks are file-based, too.
Anyway, here is a sample BackgroundSession manager:
import os.log
// Note, below I will use `os_log` to log messages because when testing background URLSession
// you do not want to be attached to a debugger (because doing so changes the lifecycle of an
// app). So, I will use `os_log` rather than `print` debugging statements because I can then
// see these logging statements in my macOS `Console` without using Xcode at all. I'll log these
// messages using this `OSLog` so that I can easily filter the macOS `Console` for just these
// logging statements.
private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: #file)
class BackgroundSession: NSObject {
var savedCompletionHandler: (() -> Void)?
static var shared = BackgroundSession()
private var session: URLSession!
private override init() {
super.init()
let identifier = Bundle.main.bundleIdentifier! + ".backgroundSession"
let configuration = URLSessionConfiguration.background(withIdentifier: identifier)
session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
}
}
extension BackgroundSession {
#discardableResult
func startUpload(for request: URLRequest, from data: Data) throws -> URLSessionUploadTask {
os_log("%{public}#: start", log: log, type: .debug, #function)
let fileURL = URL(fileURLWithPath: NSTemporaryDirectory())
.appendingPathComponent(UUID().uuidString)
try data.write(to: fileURL)
let task = session.uploadTask(with: request, fromFile: fileURL)
task.resume()
return task
}
}
extension BackgroundSession: URLSessionDelegate {
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
os_log(#function, log: log, type: .debug)
DispatchQueue.main.async {
self.savedCompletionHandler?()
self.savedCompletionHandler = nil
}
}
}
extension BackgroundSession: URLSessionTaskDelegate {
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error = error {
os_log("%{public}#: %{public}#", log: log, type: .error, #function, error.localizedDescription)
return
}
os_log("%{public}#: SUCCESS", log: log, type: .debug, #function)
}
}
extension BackgroundSession: URLSessionDataDelegate {
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didBecome downloadTask: URLSessionDownloadTask) {
os_log(#function, log: log, type: .debug)
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
os_log("%{public}#: received %d", log: log, type: .debug, #function, data.count)
}
}
And, of course, my app delegate:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
...
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: #escaping () -> Void) {
BackgroundSession.shared.savedCompletionHandler = completionHandler
}
}
Needless to say, that’s a lot to worry about with uploads in conjunction with background URLSession. If you’re uploading huge assets (e.g. videos) over slow connections, perhaps you need that. But the other (simpler) alternative is to use a default URLSession configuration, and just tell the OS that even if the user leaves your app, request a little more time to finish the upload. Just use standard completion handler pattern with default URLSession, and marry that with the techniques outlined in Extending Your App's Background Execution Time. Now, that only buys you 30 seconds or so (it used to be 3 minutes in older iOS versions), but often that’s all we need. But if you think it may take more than 30 seconds to finish the uploads, then you’ll need background URLSessionConfiguration.

Change your
let config = URLSessionConfiguration.background(withIdentifier: "MyUniqeId")
To
let config = URLSession(configuration: URLSessionConfiguration.default)
complete code will like below
let config = URLSession(configuration: URLSessionConfiguration.default)
var Boundary = "\(boundary.generateBoundaryString())_boundary"
private lazy var session: URLSession = {
let config = URLSession(configuration: URLSessionConfiguration.default)
config.isDiscretionary = true
config.sessionSendsLaunchEvents = true
return URLSession(configuration: config, delegate: self, delegateQueue: nil)
}()
func webServiceForUploadImages(urlStr:String,params:[String:String],fileUrl:String,imageData:Data,success :#escaping (AppMedia) -> Void ,failure:#escaping (NSError) -> Void) -> Void{
let url = Constant.BASE_URL + urlStr
print(url)
if(reachAbility.connection != .none){
var request = URLRequest(url: URL(string: url)!)
request.httpMethod = "POST"
request.allHTTPHeaderFields = Header.headers()
request.setValue("multipart/form-data; boundary=\(Boundary)", forHTTPHeaderField: "Content-Type")
let data = try! createBody(with: params, filePathKey: "file", paths: [fileUrl], boundary: "\(Boundary)", imageData: imageData)
session.uploadTask(with: request, from: data) { (data, response, err) in
if response != nil{
guard let response = response as? HTTPURLResponse else {return}
handleError.shared.HandleReponseTokenExpireError(dataResponse: response, success: { (response) in
})
if(err != nil){
print("\(err!.localizedDescription)")
}
guard let responseData = data else {
print("no response data")
return
}
if let responseString = String(data: responseData, encoding: .utf8) {
DispatchQueue.main.async {
let dict = Utility.jsonToDict(str: responseString)
let mediaDict = AppMedia(fromDictionary: dict as! [String : Any])
Constant.KAppDelegate.hideProgressHUD()
success(mediaDict)
}
// print("uploaded to: \(responseString)")
}
}else{
DispatchQueue.main.async {
failure(err! as NSError)
Constant.KAppDelegate.hideProgressHUD()
Constant.KAppDelegate.showErrorMessage(title: "Error", message: Constant.ServerError, msgType: .error)
}
}
}.resume()
}else{
self.showErrorMsg(str: Constant.ConnectivityError)
}
}
now call it in GCD in you viewController or what ever place you want.
DispatchQueue.global().async {
// call your webServiceForUploadImages with completion blocks
}
NOTE:
Only do if you are in need of completion blocks if completion blocks are not necessary do as suggested in exception use delegation

Related

How to check error if uploading is failed using session.uploadTask in swift?

I am uploading video file in the background but I dnon't know how to check if the video file is failed to upload. I am using URLSession to upload the file. If anybody has some idea please help me out.
private func uploadMediaWith(mediaURL: URL, mediaMimeType: MediaMimeType, AWSPresignedURL: URL) {
let finalMediaURL = mediaURL.updatedSandboxURL()
Log.echo(key: "\(self.KEY)", text: "setup upload for \(mediaURL.lastPathComponent)")
let url = AWSPresignedURL // https://dentiscope-development.s3.us-west-2.amazonaws.com/exams/42/videos/BottomLeftOutside-b121986d-bd5c-4b6f-bed2-f030978f03f0.mp4?x-amz-acl=private&x-amz-meta-exam_id=42&x-amz-meta-uuid=b121986d-bd5c-4b6f-bed2-f030978f03f0&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA5WF3XEUH4WAYWLEI%2F20211115%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20211115T070402Z&X-Amz-SignedHeaders=host%3Bx-amz-acl%3Bx-amz-meta-exam_id%3Bx-amz-meta-uuid&X-Amz-Expires=432000&X-Amz-Signature=a6ede8cd558cd1df5c5d6e62be1237f995b7ac97e235112acc263e3b0de1531f
let boundary = UUID().uuidString
var request = URLRequest(url: url)
request.httpMethod = "PUT"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
if let authToken = SignedUserInfo.sharedInstance?.authToken {
request.setValue("Authorization", forHTTPHeaderField: "Bearer " + authToken)
}
let config = URLSessionConfiguration.background(withIdentifier: boundary)
config.waitsForConnectivity = true
config.shouldUseExtendedBackgroundIdleMode = true
// config.httpMaximumConnectionsPerHost = 4
// config.networkServiceType = .background
let session = URLSession(configuration: config, delegate: self, delegateQueue: .main)
var data = Data()
if(mediaMimeType == MediaMimeType.video) {
do {
let videoData = try Data(contentsOf: finalMediaURL)
data.append(videoData)
} catch {
Log.echo(key: "MediaMimeType.video", text: error.localizedDescription)
fatalError("\(self.KEY) COULD NOT GET DATA OF THE VIDEO || \(finalMediaURL.lastPathComponent)")
}
}
request.httpBody = data
DispatchQueue.global().async {
let task = session.uploadTask(withStreamedRequest: request)
// how to check if failled to uload
self.uploadingURL = finalMediaURL
task.resume()
Log.echo(key: "\(self.KEY)", text: "execute upload for \(mediaURL.lastPathComponent) || session id \(boundary)")
}
}
If I am using this code:
let task = session.dataTask(with: request) { (data, urlResp, error) in
if let error = error {
print(error)
}
} //uploadTask(withStreamedRequest: request)
self.uploadingURL = finalMediaURL
task.resume()
then getting this error:
Terminating app due to uncaught exception 'NSGenericException', reason: 'Completion handler blocks are not supported in background sessions. Use a delegate instead.'
terminating with uncaught exception of type NSException
Using a delegate and converting it to completion block you could use something like the following:
class UploadContainer: NSObject, URLSessionTaskDelegate {
let session: URLSession
let request: URLRequest
private var completion: ((_ error: Error?) -> Void)?
private var task: URLSessionUploadTask?
private var selfRetain: Any? // This is to persist lifetime of this class for the duration of request execution
init(request: URLRequest, session: URLSession = .shared, completion: #escaping ((_ error: Error?) -> Void)) {
self.request = request
self.session = session
self.completion = completion
super.init()
self.upload()
}
private func upload() {
let task = session.uploadTask(withStreamedRequest: self.request)
self.task = task
task.delegate = self
self.selfRetain = self
task.resume()
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
self.selfRetain = nil
completion?(error)
completion = nil
}
}
Then when you generate your request you can simply call
UploadContainer(request: request) { error in
if let error = error {
print("Error occurred uploading file \(error)")
} else {
print("File was successfully uploaded")
}
}
But there are many ways to achieve this. Point being is that once you create a task you need to assign a delegate. In provided code you can see this line task.delegate = self. Whoever is assigned as delegate (in this example self as UploadContainer) needs to implement this delegate which is done by this : NSObject, URLSessionTaskDelegate. The class can now implement some methods documented in URLSessionTaskDelegate. But we are only interested in completing the upload (with or without an error) which seems to be a method called func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?). I hope this works for you.

Authorization fails (cookes not sent) in URLSessionConfiguration.background with HTTPCookieStorage.shared

In my iOS app, I create a URLSession with default configuration:
// Part #1
let urlSession = URLSession(configuration: .default)
The user logs in, the cookies are set and all API requests are Authorized and work fine. Now with the same cookies, I want to create a background URLSession for downloading large files from the same server.
// Part #2
lazy var downloadsSession: URLSession = {
let configuration = URLSessionConfiguration.background(withIdentifier:"x.bgSession")
// Use cookies from shared storage
configuration.httpCookieStorage = HTTPCookieStorage.shared
return URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
}()
The download request fails and the server returns Unauthorized. Using mitmproxy when I intercept this request, I notice that for downloadsSession, cookies are not set while making the HTTP download request.
However, now when I create a new ephemeral session, the Authorization works fine.
// Part #3
let sessionConfiguration = URLSessionConfiguration.ephemeral
sessionConfiguration.httpCookieStorage = HTTPCookieStorage.shared
session_ = URLSession(configuration: sessionConfiguration, delegate: self, delegateQueue: nil);
What is the difference between Part #2 & Part #3 in terms of Authorization (setting cookies)? Why Part #3 succeeds while Part #2 fails?
for your part #1, #2, #3
add following line to request
request.httpShouldHandleCookies=true
If it still give you the same error consult your backend developer and check download permissions for file your trying to download
for complete help you may check following code
let mainUrl = "your request url"
request.httpMethod = serviceType
request.httpShouldHandleCookies=true
let cookie = HTTPCookie.requestHeaderFields(with: HTTPCookieStorage.shared.cookies(for: mainUrl as URL)!)
request = setAccessToken_Cookie(request: request)
request.allHTTPHeaderFields = cookie
var session = URLSession(configuration: URLSessionConfiguration.default)
session = NetworkManager().configureURLSessionMethodWithDelegate(session: session)
if downloadable
{
Downloader.downloadFile(url: mainUrl as URL, session: session, request:request, completion: {
})
}
import Foundation
import UIKit
class Downloader:NSObject, URLSessionDownloadDelegate
{
class func downloadFile(url: URL,session:URLSession, request:NSMutableURLRequest, completion: #escaping () -> ()) {
var delegateSession = Downloader().configureURLSessionMethodDelegate(session: session)
let downloads = delegateSession.downloadTask(with: request as URLRequest)
downloads.resume()
}
func configureURLSessionMethodDelegate( session:URLSession) -> URLSession {
var sessionWithDelegate = session
sessionWithDelegate = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue:OperationQueue.main)
return sessionWithDelegate
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
guard let httpResponse = downloadTask.response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
print ("error : \(error according to your response code)")
return
}
do {
let documentsURL = try
FileManager.default.url(for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: false)
let savedURL = documentsURL.appendingPathComponent(downloadTask.response?.suggestedFilename ?? "noname")
print(location)
print(savedURL)
if FileManager.default.fileExists(atPath: savedURL.path)
{
AppUtility?.displayAlert(title: appName, messageText: "A file with same name already exist", delegate: nil)
}
else{
try FileManager.default.copyItem(at: location, to: savedURL)
}
} catch {
print ("file error: \(error)")
}
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)
{
guard let httpResponse = downloadTask.response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
print ("server error")
return
}
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
DispatchQueue.main.async(execute: { () -> Void in
if(error != nil)
{
//handle the error
print("Download completed with error: \(error!.localizedDescription)");
}
})
}
}

Upload video to Vimeo using URLSession

Im trying to upload a video to Vimeo , using their rest API.
First I make a POST, to create the video on the API, then I select the video from camera, and send a PATCH with some parameters.
The problem is, the video seems to upload with success, I get a HTML without any errors that seems a success, but in the site doesn't show the video, and the KB size is only 0.
Here's my PATCH method :
let request = NSMutableURLRequest(url: "https://api.vimeo.com/me/videos")
request.httpMethod = "PATCH"
request.cachePolicy = .reloadIgnoringLocalCacheData
request.httpBody = mediaData // Data()
request.setValue("Upload-Offset", forHTTPHeaderField: "0")
request.setValue("Content-Type", forHTTPHeaderField: "application/offset+octet-stream")
request.setValue("Authorization", forHTTPHeaderField: "Bearer 1233131312")
request.setValue("Tus-Resumable", forHTTPHeaderField: "1.0.0")
let newTask = session.uploadTask(withStreamedRequest: request)
newTask.resume()
and already tried with:
request.httpBodyStream = InputStream(data: mediaData)
or:
session.uploadTask(with: request, from: data)
Heres is the documentation from VIMEO :
https://developer.vimeo.com/api/upload/videos
Can anyone have an example or snippet that actually works?
[UPDATE]:
The class that make the request:
import Foundation
case get
case post
case put
case delete
case patch
var verbName: String {
switch self {
case .get:
return "GET"
case .post:
return "POST"
case .put:
return "PUT"
case .delete:
return "DELETE"
case .patch:
return "PATCH"
}
}
var containsBody: Bool {
return self == .post || self == .put || self == .patch
}
}
final class MediaUploader: URLSessionTaskDelegate, URLSessionDataDelegate {
private let dispatchGroup = DispatchGroup()
private let globalSSLEnabled: Bool = true
var outputStream: OutputStream? = nil
private var buffer:NSMutableData = NSMutableData()
convenience init?(domain: String) {
self.init(domain: domain)
}
func request(verb: HTTPVerb, action: String, parameters: JSONDictionary = [:], headers: [String : String] = [:], sslEnabled: Bool = true, media: MediaData, completion: #escaping (_ response: HTTPResponse) -> Void) {
let sslEnabled = self.globalSSLEnabled || sslEnabled
guard let request = buildRequest(verb: verb, action: action, parameters: parameters, media: media, sslEnabled: sslEnabled) else {
return
}
headers.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) }
let session = buildURLSession(sslEnabled: sslEnabled)
conditionalLog(items: "REQUEST [\(verb.verbName)]")
conditionalLog(items: "URL: \(request.url?.absoluteString ?? "[invalid]")")
conditionalLog(items: "Headers: \(String(describing: request.allHTTPHeaderFields))")
conditionalLog(items: "Parameters: \(parameters)")
createSessionWithDataTask(session: session, request: request as URLRequest, mediaData: media, completion: completion)
}
func buildURLSession(sslEnabled: Bool) -> URLSession {
if !sslEnabled {
return URLSession(configuration: .default)
}
let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
return session
}
func createSessionWithDataTask(session: URLSession, request: URLRequest, mediaData: MediaData,completion: #escaping (HTTPResponse) -> Void) {
let queue = DispatchQueue(label: "com.lassie.dispatchgroup", attributes: .concurrent, target: nil)
dispatchGroup.enter()
queue.async(group: dispatchGroup) {
self.dataTaskWorker(session: session, request: request as URLRequest, mediaData: mediaData, completion: completion)
self.dispatchGroup.leave()
}
}
func dataTaskWorker(session: URLSession, request: URLRequest, mediaData: MediaData, completion: #escaping (_ response: HTTPResponse) -> Void) {
guard let data = mediaData.data() else {
return
}
// let newTask = session.uploadTask(withStreamedRequest: request)
// let newTask = session.uploadTask(with: request, from: data)
//session.uploadTask(with: request, from: data)
let task = session.uploadTask(withStreamedRequest: request) { data, response, error in
self.conditionalLog(items: "RESPONSE: \(String(describing: response))")
if let eRROR = error {
self.conditionalLog(items: "ERROR : \(String(describing: eRROR))")
}
self.parseResponse(data: data, response: response, error: error, completion: completion)
session.finishTasksAndInvalidate()
}
newTask.resume()
}
func buildRequest(verb: HTTPVerb, action: String, parameters: JSONDictionary = [:], media: MediaData, sslEnabled: Bool) -> NSMutableURLRequest? {
let suffix = buildAction(verb: verb, action: action, parameters: parameters)
guard let fullUrl = suffix.isEmpty ? baseURL(sslEnabled: sslEnabled) : URL(string: suffix, relativeTo: baseURL(sslEnabled: sslEnabled)), let mediaData = media.data() else {
assert(false, "Invalid url/parameters")
return nil
}
let request = NSMutableURLRequest(url: fullUrl)
request.httpMethod = verb.verbName
request.cachePolicy = .reloadIgnoringLocalCacheData
request.httpBody = mediaData
// request.httpBodyStream = InputStream(data: mediaData)
return request
}
// MARK: URLSessionDataTask
func urlSession(_ session: URLSession, task: URLSessionTask, needNewBodyStream completionHandler: #escaping (InputStream?) -> Void) {
self.closeStream()
var inStream: InputStream? = nil
var outStream: OutputStream? = nil
Stream.getBoundStreams(withBufferSize: 4096, inputStream: &inStream, outputStream: &outStream)
self.outputStream = outStream
completionHandler(inStream)
}
private func closeStream() {
if let stream = self.outputStream {
stream.close()
self.outputStream = nil
}
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
buffer.append(data)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
let percentageSent = Double(totalBytesSent) / Double(totalBytesExpectedToSend)
print("PERCENTAGE - \(percentageSent)")
}
private func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: #escaping (URLSession.ResponseDisposition) -> Void) {
completionHandler(.allow)
}
private func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let e = error {
print("ERROR")
} else {
self.conditionalLog(items: "RESPONSE DATA: \(String(data: buffer as Data, encoding: .utf8))")
}
}
}
You are using session.uploadTask(withStreamedRequest: request). The reason the file size is 0 on backend is you didn't close the stream, something like inputStream.finished = true. So you have to figure out when you want to close that stream.
Anyways, since you already have the data ready. i would say you can use uploadTask(with:from:completionHandler:), or even switching to uploadTask(with:fromFile:completionHandler:)

Download image directly to a photo album

Currently I'm using Alamofire for network requests. How can I download an image directly to a photo album that already exists using the PHPhotoLibrary from Photos.framework?
P.S.: I don't mind to have a solution using NSURLSession by itself.
Considerations: The file can't be stored in disk temporarily. I want the data in memory and save it once into the disk using the Photos.framework.
I figure it out by myself. I implemented it with a dataTaskWithRequest from NSURLSession.
Initially I thought using the NSData(contentsOfURL:) but the contentsOfURL method does not return until the entire file is transferred. It should only be used for local files and not for remote ones because if you're loading a large content and the device has a slow connection then the UI will be blocked.
// Avoid this!
let originalPhotoData = NSData(contentsOfURL: NSURL(string: "http://photo.jpg")!)
So, it is possible to load the content into a NSData with a data task. One solution can be like:
let request = NSMutableURLRequest(URL: NSURL(string: urlString.URLString)!)
request.allHTTPHeaderFields = [
"Content-Type": "image/jpeg"
]
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
if let e = error {
print(e.description)
return
}
guard let image = UIImage(data: data) else {
print("No image")
return
}
// Success
// Note: does not save into an album. I removed that code to be more concise.
PHPhotoLibrary.sharedPhotoLibrary().performChanges {
let creationRequest = PHAssetChangeRequest.creationRequestForAssetFromImage(image)
}, completionHandler: { (success : Bool, error : NSError?) -> Void in
if success == false, let e = error){
print(e)
}
}
}
task.resume()
The data is only available in the final stage but you can instead access the data and see the progress using a session delegate.
class Session: NSObject, NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate {
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let queue = NSOperationQueue.mainQueue()
var session = NSURLSession()
var buffer = NSMutableData()
var expectedContentLength = 0
override init() {
super.init()
session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: queue)
}
func dataTaskWithRequest(request: NSURLRequest) -> NSURLSessionDataTask {
// The delegate is only called if you do not specify a completion block!
return session.dataTaskWithRequest(request)
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: (NSURLSessionResponseDisposition) -> Void) {
buffer = NSMutableData()
expectedContentLength = Int(response.expectedContentLength)
print("Expected content length:", expectedContentLength)
completionHandler(NSURLSessionResponseDisposition.Allow)
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
buffer.appendData(data)
let percentageDownloaded = Float(buffer.length) / Float(expectedContentLength)
print("Progress: ", percentageDownloaded)
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
print("Finished with/without error \(error)")
// Use the buffer to create the UIImage
}
}

How to use asynchronous methods (JSON Restful services) in iOS?

I have an iOS application that sync data from a JSON restful web service. This method is called from an external class (not UI controller). This class has a sync method and It sends and retrieves data without any problem. My problem is how do I pause the UI till I get my result.
The following would provide the idea about the code.
UIController Class
let C : Customer = Customer(UserName: UserName!, Password: Password!)
let S : Syncronization = Syncronization()
S.Sync(C)
Syncronization class
Class Syncronization : NSObject, NSURLSessionDataDelegate
func Sync(C : Customer){
var datastr = ""
datastr = "http://192.168.248.134:8008/MobileWeb.svc/GetFirstTimeSync/" + C.UserName + "/" + C.Password
let url:NSURL = NSURL(string: datastr)!
self.buffer = NSMutableData()
let defaultConfigObject:NSURLSessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session:NSURLSession = NSURLSession(configuration: defaultConfigObject, delegate: self, delegateQueue: NSOperationQueue.mainQueue())
let req:NSMutableURLRequest = NSMutableURLRequest(URL: url)
req.HTTPMethod = "POST"
session.dataTaskWithURL(url).resume()
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
print("Recieved with data")
buffer.appendData(data)
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
if error == nil {
print("Download Successful")
print("Done with Bytes " + String(buffer.length))
self.parseJSONA(self.buffer)
}
else {
print("Error %#",error!.userInfo);
print("Error description %#", error!.localizedDescription);
print("Error domain %#", error!.domain);
}
}
func parseJSONA(data:NSMutableData) {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as! Array<AnyObject>
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
}
I have tried dispacther methods, but I believe I do not know how to use that so far cause most of the examples have down the services & data exchange on UI Controllers.
Any kind of help is appreciated. Thanks
You can give the Sync class a completion handler closure.
In your UIViewController:
S.completion = {
information in
UIElement.updateWith(information)
}
and of course you'll need to add a member to your Syncronization class:
var completion:((information:String)->())!
and you can call completion("here's some info!") from inside parseJSON() or URLSession() of the Syncronization class
Here's some reading on closures in Swift
If I understand you correctly, you want to be able to do something in your UI based on the outcome of your call to S.Sync(C)
One way of doing that is to include a closure as a parameter to your Sync function.
Here's how I would do that (Disclaimer...I haven't checked everything in a compiler, so there might be errors along the way. See how far you get, and if there are problems, just write again :-)):
enum SynchronizationResult {
case Success(Array<AnyObject>)
case Failure(NSError)
}
class Syncronization : NSObject, NSURLSessionDataDelegate {
var functionToExecuteWhenDone: ((SynchronizationResult) -> Void)?
func Sync(C : Customer, callback: (SynchronizationResult) -> Void){
functionToExecuteWhenDone = callback
var datastr = ""
datastr = "http://192.168.248.134:8008/MobileWeb.svc/GetFirstTimeSync/" + C.UserName + "/" + C.Password
let url:NSURL = NSURL(string: datastr)!
self.buffer = NSMutableData()
let defaultConfigObject:NSURLSessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session:NSURLSession = NSURLSession(configuration: defaultConfigObject, delegate: self, delegateQueue: NSOperationQueue.mainQueue())
let req:NSMutableURLRequest = NSMutableURLRequest(URL: url)
req.HTTPMethod = "POST"
session.dataTaskWithURL(url).resume()
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
print("Recieved with data")
buffer.appendData(data)
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
if error == nil {
print("Download Successful")
print("Done with Bytes " + String(buffer.length))
self.parseJSONA(self.buffer)
} else {
print("Error %#",error!.userInfo);
print("Error description %#", error!.localizedDescription);
print("Error domain %#", error!.domain);
let result = SynchronizationResult.Failure(error!)
if let functionToExecuteWhenDone = functionToExecuteWhenDone {
functionToExecuteWhenDone(result)
}
}
}
func parseJSONA(data:NSMutableData) {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as! Array<AnyObject>
let result = SynchronizationResult.Success(json)
if let functionToExecuteWhenDone = functionToExecuteWhenDone {
functionToExecuteWhenDone(result)
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
let result = SynchronizationResult.Failure(error)
if let functionToExecuteWhenDone = functionToExecuteWhenDone {
functionToExecuteWhenDone(result)
}
}
}
}
So...we introduce an Enum called SynchronizationResult to handle the outcome of fetching data.
We then add a function to be called when done, as a parameter to the Sync function:
func Sync(C : Customer, callback: (SynchronizationResult) -> Void)
This method will be called with a SynchronizationResult as a parameter and returns void.
We store that callback in functionToExecuteWhenDone for later usage.
Depending on whether you see any errors along the way or everything is sunshine, we generate different SynchronizationResult values along the way and call your functionToExecuteWhenDone with the current SynchronizationResult when we are ready (when parsing is done or we have failed)
And in your ViewController you'd do something along the lines of
let C : Customer = Customer(UserName: UserName!, Password: Password!)
let S : Syncronization = Syncronization()
S.Sync(C) { (result) in
switch result {
case .Success(let json):
//Your code to update UI based on json goes here
case .Failure(let error):
//Your code to handle error goes here
}
}
I hope this makes sense and is what you needed.
this might help you
https://thatthinginswift.com/background-threads/
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
// do downloading or sync task here
dispatch_async(dispatch_get_main_queue()) {
// update some UI with downloaded data/sync data
}
}
let qualityOfServiceClass = QOS_CLASS_BACKGROUND
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
print("This is run on the background queue")
dispatch_async(dispatch_get_main_queue(), { () -> Void in
print("This is run on the main queue, after the previous code in outer block")
})
})
Found here

Resources