Download image directly to a photo album - ios

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
}
}

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.

Observing progress while using URLSessionDataTask and URLSessionDownloadTask both for same remote url

I'm using URLSessionDataTask to show an image, and using URLSessionDownloadTask to download the image on my app. Also I needed to observe download progress, so I use KVO at Progress's fractionCompleted.
The thing is fractionCompleted value just has 0.05 and 1.0. I think this issue happened for using same remote url. Because when I don't use URLSessionDataTask(I can't show the image though), I get right progress update.
Why is it happened? Is there any reference?
Edited
First, I don't want to use URLSession delegate to get progress. I'm using two VCs(first one is RemoteGridViewController using URLSessionDataTask to show image and second one is MediaViewerController). MediaViewerController delegate to first VC for downloading the image on camera roll. I know I can use URLSession delegate method to notify progress by using NotificationCenter or something like that, but I don't want. This way makes strong coupling(?) between the VCs(I guess...)
Second, RemoteGridViewController use URLSession to show an image by URLSessionDataTask and cache it.
guard let url = URL(string: url) else {
completionHandler(.failure(NetworkManagerError.urlError))
return
}
var req = URLRequest(url: url)
req.cachePolicy = .returnCacheDataElseLoad
if let cachedResponse = sharedCache?.cachedResponse(for: req) {
completionHandler(.success(cachedResponse.data))
} else {
sessionDataTask = session.dataTask(with: req) { [weak self] (data, response, error) in
if let error = error {
completionHandler(.failure(error))
print(error.localizedDescription)
return
}
guard let response = response as? HTTPURLResponse, let data = data else {
completionHandler(.failure(NetworkManagerError.dataError))
return
}
self?.sharedCache?.storeCachedResponse(CachedURLResponse(response: response, data: data), for: req)
completionHandler(.success(data))
}
sessionDataTask?.resume()
}
Third, MediaViewerController call MediaViewerDelegate method, which is downloadImage(itemAt:for:) to download an image.
extension RemoteGridViewController: MediaViewerDelegate {
func downloadImage(itemAt indexPath: IndexPath, for mediaViewer: MediaViewerController) {
let data = unsplashData[indexPath.item]
let urlString = data.regular
guard let url = URL(string: urlString) else { return }
mediaViewer.presentProgressView()
networkManager.downloadTaskForImage(with: url, progressHandler: mediaViewer.observe(_:)) { result in
switch result {
case .success(let image):
DispatchQueue.main.async {
mediaViewer.toastView(with: true)
}
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
case .failure(let error):
DispatchQueue.main.async {
mediaViewer.toastView(with: false)
}
print(error.localizedDescription)
}
}
}
}
class NetworkManager {
private let session: URLSession!
private var sessionDownloadTask: URLSessionDownloadTask?
func downloadTaskForImage(with url: URL, progressHandler: (Progress?) -> (), completionHandler: #escaping (Result<UIImage, Error>) -> ()) {
sessionDownloadTask = session.downloadTask(with: url) { (location, response, error) in
if let error = error {
completionHandler(.failure(error))
return
}
guard let response = response as? HTTPURLResponse, (200...299).contains(response.statusCode) else {
completionHandler(.failure(NetworkManagerError.responseError))
return
}
guard let location = location else {
completionHandler(.failure(NetworkManagerError.urlError))
return
}
do {
let data = try Data(contentsOf: location)
if let image = UIImage(data: data) {
completionHandler(.success(image))
} else {
completionHandler(.failure(NetworkManagerError.dataError))
}
} catch let error {
completionHandler(.failure(error))
}
}
progressHandler(sessionDownloadTask?.progress)
sessionDownloadTask?.resume()
}
}
You can track for the downloaded bytes to total expected bytes to track the progress of downloading image.
Use following delegate method:
URLSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)
and following snippet:
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
// println("download task did write data")
let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
//Your code to download and save image to camera role
}
}
I hope this may have resolved your query to check for exact progress with downloading.

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

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

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

NSURLSession URL Response is not cached when larger than a few kilobytes

I am pretty new to programming in IOS with Cocoa and I am using Swift. I fetch data from a JSON API using an NSURLSession data session with custom delegate, not closures. The reason for using custom delegate is that I have to do basic authentication and I also inject a custom cache-control header to control caching behavior (my API doesn’t include any caching related headers in the response at all).
All this works perfectly but only for requests for which the URLSession:dataTask:didReceiveData: method is called only once. As soon as I get larger responses (some 20-30kBytes) that call the didReceivedData method several times, the URLSession:dataTask:willCacheResponse:completionHandler: method doesn’t get called at all, and therefore my response doesn’t get cached. Re-issuing the same request within 5 minutes will issue a request to the server again, which doesn’t happen for requests whose responses only call didReceiveData one single time. The URLSession:task:didCompleteWithError: method is correctly called and proceeded in all cases.
The documentation of the URLSession:dataTask:willCacheResponse:completionHandler: method (https://developer.apple.com/library/IOs/documentation/Foundation/Reference/NSURLSessionDataDelegate_protocol/index.html#//apple_ref/occ/intfm/NSURLSessionDataDelegate/URLSession:dataTask:willCacheResponse:completionHandler:) says this method is only called if the NSURLProtocol handling the request decides to do so, but I don’t really understand what to do to make this happen.
Any feedback and ideas are very welcome!
This is the code that issues the HTTP request:
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
config.URLCache = NSURLCache.sharedURLCache()
//config.URLCache = NSURLCache(memoryCapacity: 512000000, diskCapacity: 1000000000, diskPath: "urlCache")
let urlString = apiUrlForFilter(filter, withMode: mode, withLimit: limit, withOffset: offset)
let url = NSURL(string: urlString)
var policy: NSURLRequestCachePolicy?
if ignoreCache == true {
policy = .ReloadIgnoringLocalCacheData
} else {
policy = .UseProtocolCachePolicy
}
let request = NSURLRequest(URL: url!, cachePolicy: policy!, timeoutInterval: 20)
let session = NSURLSession(configuration: config, delegate: self, delegateQueue: nil)
let task = session.dataTaskWithRequest(request)
task.resume()
I have the following delegate functions implemented:
URLSession:didReceiveChallenge:completionHandler: to handle SSL certificate trust,
URLSession:task:didReceiveChallenge:completionHandler: to handle basic authentication
URLSession:dataTask:didReceiveResponse:completionHandler: to read some specific headers and save them as instance variables
additionally, the important ones with code:
URLSession:dataTask:didReceiveData: to accumulate data as it arrives for larger HTTP request responses:
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
if receivedData == nil {
receivedData = NSMutableData()
}
receivedData!.appendData(data)
println("did receive data: \(receivedData!.length) bytes")
}
URLSession:dataTask:willCacheResponse:completionHandler: to inject my own Cache-Control header:
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, willCacheResponse proposedResponse: NSCachedURLResponse, completionHandler: (NSCachedURLResponse!) -> Void) {
println("willCacheResponse was called")
let response: NSURLResponse = proposedResponse.response
let httpResponse = response as NSHTTPURLResponse
var headers = httpResponse.allHeaderFields
var modifiedHeaders = headers
modifiedHeaders.updateValue("max-age=300", forKey: "Cache-Control")
let modifiedResponse = NSHTTPURLResponse(URL: httpResponse.URL!, statusCode: httpResponse.statusCode, HTTPVersion: "HTTP/1.1", headerFields: modifiedHeaders)
let cachedResponse = NSCachedURLResponse(response: modifiedResponse!, data: proposedResponse.data, userInfo: proposedResponse.userInfo, storagePolicy: proposedResponse.storagePolicy)
completionHandler(cachedResponse)
}
URLSession:task:didCompleteWithError: to check the complete response for errors and call a callback closure this class gets by initialization to further proceed the result data:
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
session.finishTasksAndInvalidate()
if error != nil {
var string: String?
if errorString != nil {
string = errorString
} else {
string = Helpers.NSURLErrorDomainErrorForCode(error!.code)
}
errorCallback(string!)
return
}
if receivedData == nil {
errorCallback("the query returned an empty result")
return
}
var jsonError: NSError?
let results: AnyObject! = NSJSONSerialization.JSONObjectWithData(receivedData!, options: NSJSONReadingOptions.AllowFragments, error: nil)
if results == nil {
errorCallback("the data returned was not valid JSON")
return
}
let jsonParsed = JSONValue.fromObject(results)
if let parsedAPIError = jsonParsed!["error"]?.string {
errorCallback("API error: \(parsedAPIError)")
return
}
callback(jsonParsed!, self.serverTime!)
}

Resources