When session is set as .init(configuration:..., delegate:self, delegateQueue:NSOperationQueue.mainQueue()) for proxy server redirect, NSURLSessionDataTask.resume() does not result in execution of task. When session is set as .sharedSession(), task executes as expected.
**kCFStreamPropertyHTTPProxyHost etc. have been deprecated. Maybe this affects NSURLSessionConfiguration in a way that prevents execution of task?
class ConnectionManager: NSURLSession, NSURLSessionDelegate {
.
.
.
if shouldUseProxy {
let proxyEnable = NSNumber(int: 1) as CFNumber
let proxyDict: [NSObject:AnyObject] = [
kCFNetworkProxiesHTTPEnable: proxyEnable,
kCFStreamPropertyHTTPProxyHost: proxyHost,
kCFStreamPropertyHTTPProxyPort: proxyPort,
kCFStreamPropertyHTTPSProxyHost: proxyHost,
kCFStreamPropertyHTTPSProxyPort: proxyPort,
kCFProxyTypeKey: kCFProxyTypeHTTPS,
kCFProxyUsernameKey: proxyUser,
kCFProxyPasswordKey: proxyPW
]
let config = NSURLSessionConfiguration.ephemeralSessionConfiguration()
config.connectionProxyDictionary = proxyDict
self.session = NSURLSession.init(configuration: config, delegate: self, delegateQueue: NSOperationQueue.mainQueue())
} else {
self.session = NSURLSession.sharedSession()
}
self.task = self.session.dataTaskWithRequest(request) {
(data, response, error) in
if error == nil {
self.cookies = NSHTTPCookieStorage.sharedHTTPCookieStorage().cookiesForURL(response!.URL!)!
self.httpResponse = (response as? NSHTTPURLResponse)!
self.statusCode = (self.httpResponse!.statusCode)
guard error == nil && data != nil else {
print(error)
return
}
do {
if self.statusCode == 200 {
self.contentsOfURL = try NSString(contentsOfURL: self.URL, encoding: NSUTF8StringEncoding) as String
}
} catch {
}
}
}
self.task?.resume()
.
.
.
}
Setting the configuration
In most cases the default configuration works unless you need something special.
let sessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: sessionConfiguration,
delegate: self,
delegateQueue: operationQueue)
Pls refer Apple's documentation: https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSURLSession_class/#//apple_ref/doc/uid/TP40013435-CH1-SW7
Redirect response
NSURLSessionDelegate has the below mentioned function which gets called when a redirect happens.
func URLSession(session: NSURLSession, task: NSURLSessionTask, willPerformHTTPRedirection response: NSHTTPURLResponse, newRequest request: NSURLRequest, completionHandler: (NSURLRequest?) -> Void) {
//self.completionHandler is some completion handler you have defined, it is call back function.
if cancelled {
self.completionHandler(nil, redirectRequest:nil, wasCancelled: true)
}
else {
self.completionHandler(nil, redirectRequest:request, wasCancelled: false)
}
finished = true
executing = false
}
There is a parameter called request which contains the new URLRequest. Now you have to initiate a new session task with this new request
Challenge
This function is part of NSURLSessionDelegate
Whenever challenge is received the following function is called. Depending on the type of challenge, you have to handle it appropriately.
See the commented out code for NSURLAuthenticationMethodHTTPBasic and NSURLAuthenticationMethodHTTPDigest
func URLSession(session: NSURLSession,
didReceiveChallenge challenge: NSURLAuthenticationChallenge,
completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
print("Challenge:")
if(challenge.previousFailureCount == 0) {
let credential = credentialForChallenge(challenge)
print("Use Credential ....\n")
completionHandler(.UseCredential, credential)
}
else {
print("Previous Failure Count = \(challenge.previousFailureCount)")
print("Cancelling Challenge\n")
challenge.sender?.cancelAuthenticationChallenge(challenge)
}
}
//Custom method to get the credential for the challenge
//In your case you would be interested in NSURLAuthenticationMethodHTTPBasic
func credentialForChallenge(challenge : NSURLAuthenticationChallenge) -> NSURLCredential? {
//https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/URLLoadingSystem/Articles/AuthenticationChallenges.html
//challenge knows:
//what triggered the challenge
//how many attempts were made for the challenge - previousFailureCount
//any previous attempted credentials - proposedCredential
//the NSURLProtectionSpace that requires the credentials
//sender of the challenge
//#Respond
//Get authentication method
let credential : NSURLCredential?
print("Method = \(challenge.protectionSpace.authenticationMethod)")
switch(challenge.protectionSpace.authenticationMethod) {
case NSURLAuthenticationMethodHTTPBasic:
// HTTP basic authentication
// prompt user for username and password
// let credential = NSURLCredential(user: <#T##String#>, password: <#T##String#>, persistence: <#T##NSURLCredentialPersistence#>)
credential = nil
case NSURLAuthenticationMethodHTTPDigest:
// HTTP digest authentication
// prompt user for username and password
// let credential = NSURLCredential(user: <#T##String#>, password: <#T##String#>, persistence: <#T##NSURLCredentialPersistence#>)
credential = nil
case NSURLAuthenticationMethodClientCertificate:
// Client certificate authentication
// let credential = NSURLCredential(identity: <#T##SecIdentity#>, certificates: <#T##[AnyObject]?#>, persistence: <#T##NSURLCredentialPersistence#>)
credential = nil
case NSURLAuthenticationMethodServerTrust:
// Server trust authentication
if let serverTrustExists = challenge.protectionSpace.serverTrust {
credential = NSURLCredential(trust: serverTrustExists)
}
else {
credential = nil
}
default:
credential = nil
}
return credential
}
Related
I was trying to load a self signed url inside Webview in a iOS app. Other urls are loading perfectly inside my webview.
I have added inside my info.plist file but getting error :-
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
And here is my full error :-
2019-01-08 12:26:52.386721+0530 Webview demo[761:27005] TIC SSL Trust
Error [2:0x282e90480]: 3:0
2019-01-08 12:26:52.405216+0530 Webview demo[761:27005]
NSURLSession/NSURLConnection HTTP load failed
(kCFStreamErrorDomainSSL, -9807)
2019-01-08 12:26:52.405283+0530 Webview demo[761:27005] Task
.<0> HTTP load failed (error
code: -1202 [3:-9807])
2019-01-08 12:26:52.405519+0530 Webview demo[761:27003]
NSURLConnection finished with error - code -1202
I am new to iOS please help me to understand and solve the problem.
Here is my ViewController :-
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var webView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://stackoverflow.com")
if let unwrappedUrl = url{
let request = URLRequest(url : unwrappedUrl)
let session = URLSession.shared
let task = session.dataTask(with: request) { (data, response , error) in
if error == nil {
DispatchQueue.main.async {
self.webView.loadRequest(request)
}
}
}
task.resume()
}
}
}
All you have to do is add some coding stuff instead of adding keys - value in Info.plist
let serverTrustPolicies: [String: ServerTrustPolicy] = [
"your site": .disableEvaluation
]
Add above stuff in your session manager.
var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling
If you use alamofire them you can utilize the below method,
func urlSession(
_ session: URLSession,
task: URLSessionTask,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
{
var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling
var credential: URLCredential?
if let taskDidReceiveChallenge = taskDidReceiveChallenge {
(disposition, credential) = taskDidReceiveChallenge(session, task, challenge)
} else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
let host = challenge.protectionSpace.host
if
let serverTrustPolicy = session.serverTrustPolicyManager?.serverTrustPolicy(forHost: host),
let serverTrust = challenge.protectionSpace.serverTrust
{
if serverTrustPolicy.evaluate(serverTrust, forHost: host) {
disposition = .useCredential
credential = URLCredential(trust: serverTrust)
} else {
disposition = .cancelAuthenticationChallenge
}
}
} else {
if challenge.previousFailureCount > 0 {
disposition = .rejectProtectionSpace
} else {
credential = self.credential ?? session.configuration.urlCredentialStorage?.defaultCredential(for: challenge.protectionSpace)
if credential != nil {
disposition = .useCredential
}
}
}
completionHandler(disposition, credential)
}
I am fetching new access token with alamofire's retrier and adapt protocol. I am able to fetch a new token but sometimes when another thread is calling the same method, it is not working and the request fails even when new access Token is generated.
I have just changed the example and now I am using synchronous request to fetch access Token as I don't want to send an extra request in adapt if I get to know token is invalid.
The strange issue is that when I print the response on the failed request, I see that request still had the old token in the header. What am I missing here?
func isTokenValid() -> Bool {
return Date() < self.expiryTime
}
func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
var urlRequest = urlRequest
urlRequest = processRequest(urlRequest: urlRequest)
return urlRequest
}
func processRequest(urlRequest: URLRequest) -> URLRequest {
DDLogInfo("******access token : \(self.accessToken)***************")
DDLogInfo("***** expiry Time: \(self.expiryTime)***************")
var urlRequest = urlRequest
lock.lock(); defer { DDLogInfo( "Thread UnLocked ⚡️: \(Thread.current)\r" + "🏭: \(OperationQueue.current?.underlyingQueue?.label ?? "None")\r")
lock.unlock()
}
DDLogInfo( "Thread Locked ⚡️: \(Thread.current)\r" + "🏭: \(OperationQueue.current?.underlyingQueue?.label ?? "None")\r")
if !isTokenValid() {
let _ = self.refreshAccessToken()
}
urlRequest = self.appendToken(urlRequest: urlRequest)
DDLogInfo("here \(urlRequest)")
return urlRequest
}
func appendToken(urlRequest: URLRequest) -> URLRequest {
if let urlString = urlRequest.url?.absoluteString, urlString.hasPrefix(baseURLString) {
var urlRequest = urlRequest
DDLogInfo("token appended : \(self.accessToken)")
urlRequest.setValue(self.accessToken, forHTTPHeaderField: Constants.KeychainKeys.accessToken)
}
return urlRequest
}
// MARK: - RequestRetrier
func handleFailedRequest(_ completion: #escaping RequestRetryCompletion) {
requestsToRetry.append(completion)
if !isRefreshing {
lock.lock()
print( "Thread Locked⚡️: \(Thread.current)\r" + "🏭: \(OperationQueue.current?.underlyingQueue?.label ?? "None")\r")
let succeeded = self.refreshAccessToken()
self.requestsToRetry.forEach {
print("token fetched \(succeeded): \(self.accessToken)")
$0(succeeded, 0.0)
}
self.requestsToRetry.removeAll()
DDLogInfo( "Thread UnLocked⚡️: \(Thread.current)\r" + "🏭: \(OperationQueue.current?.underlyingQueue?.label ?? "None")\r")
lock.unlock()
}
}
func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: #escaping RequestRetryCompletion) {
if let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401, request.retryCount < 3 {
handleFailedRequest(completion)
} else {
completion(false, 0.0)
}
}
func updateTokens (accessToken: String, refreshToken: String, accessTokenExpiresIn: Double) {
self.accessToken = accessToken
self.refreshToken = refreshToken
let expiryDate = Date(timeIntervalSinceNow: accessTokenExpiresIn - Constants.KeychainKeys.expirationBuffer)
AppSettings.sharedInstance.tokenExpiryTime = expiryDate
self.expiryTime = expiryDate
do {try keychainWrapper.save(values: ["accessToken": self.accessToken, "refreshToken": self.refreshToken])} catch {
DDLogError("unable to save accessToken")
}
}
// MARK: - Private - Refresh Tokens
fileprivate func refreshAccessToken() -> Bool {
DDLogInfo("^^^^^^^^")
Thread.callStackSymbols.forEach { DDLogInfo($0) }
var success = false
guard !isRefreshing else { return success }
let refreshRequest = URLRequestConfigurations.configRefreshProviderAgent(refreshToken: self.refreshToken)
let result = URLSession.shared.synchronousDataTask(with: refreshRequest)
self.isRefreshing = false
do {
if let data = result.0, let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String : Any] {
if let accessToken = json["accessToken"] as? String, let refreshToken = json["refreshToken"] as? String, let time = json["accessTokenExpiresIn"] as? Double {
updateTokens(accessToken: accessToken, refreshToken: refreshToken, accessTokenExpiresIn: time)
success = true
} else {
DDLogError("unable to find tokens/expiryInterval from refresh request")
}
} else {
DDLogError("unable to receive data from refresh request")
}
} catch {
DDLogError("unable to parse json response from refersh token request")
}
return success
}
Can you check by creating singleton class for network activity. The singleton pattern guarantees that only one instance of a class is instantiated. Like this way:-
open class NetworkHelper {
class var sharedManager: NetworkHelper {
struct Static{
static let instance: NetworkHelper = NetworkHelper()
}
return Static.instance
}
.... Put you network call method here
}
I have already faced the same issue with AFNetworking. unfortunately, Reasons behind are very silly. After 2-3 days R&D, I found it is happens due to coockies and caches.
Let's understand basic about Alamofire
Alamofire is basically a wrapper around NSURLSession. Its manager uses a default NSURLSessionConfiguration by calling `defaultSessionConfiguration()
NSURLSessionConfiguration reference for defaultSessionConfiguration() says:
The default session configuration uses a persistent disk-based cache
(except when the result is downloaded to a file) and stores
credentials in the user’s keychain. It also stores cookies (by
default) in the same shared cookie store as the NSURLConnection and
NSURLDownload classes.
Just disable caching for your API by using below code.
Disabling the URLCache
let manager: Manager = {
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.URLCache = nil
return Manager(configuration: configuration)
}()
Or you can also Configuring the Request Cache Policy
let manager: Manager = {
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.requestCachePolicy = .ReloadIgnoringLocalCacheData
return Manager(configuration: configuration)
}()
Solution: Just disable chaching for your APIs. It willl work for me. In your case may be it will for you.
I found the answer to the problem.
In adapt method I had to use a different variable for urlRequest as it was not modifying the request when using the same variable name. As you can see below I changed the variable to "mutableRequest"
func appendToken(urlRequest: URLRequest) -> URLRequest {
var mutableRequest = urlRequest
if let urlString = urlRequest.url?.absoluteString, urlString.hasPrefix(baseURLString) {
DDLogInfo("token appended : \(self.accessToken)")
mutableRequest.setValue(self.accessToken, forHTTPHeaderField: Constants.KeychainKeys.accessToken)
}
return mutableRequest
}
I have an API I am trying to connect to and the server is Windows Authentication.
I am trying to use URLSession with URLCredential with the delegate methods
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void){
var disposition: URLSession.AuthChallengeDisposition = URLSession.AuthChallengeDisposition.performDefaultHandling
var credential:URLCredential?
print(challenge.protectionSpace.authenticationMethod)
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
if (credential != nil) {
disposition = URLSession.AuthChallengeDisposition.useCredential
}
else
{
disposition = URLSession.AuthChallengeDisposition.performDefaultHandling
}
}
else
{
disposition = URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge
}
completionHandler(disposition, credential);
}
This code runs twice as after doing some printing is because there are two Authentication Methods:
NSURLAuthenticationMethodServerTrust and NSURLAuthenticationMethodNTLM when it runs through the NSURLAuthenticationMethodServerTrust everything is fine, but when it runs NSURLAuthenticationMethodNTLM I get an error on this line:
credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
saying this:
fatal error: unexpectedly found nil while unwrapping an Optional value
but only when I change this condition from
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
to
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodNTLM {
What Am I doing wrong?
Here is the method I am using to try to connect to that API
func loginUser(_ username: String, password: String, completion: #escaping (_ result: Bool) -> Void)
{
//Create request URL as String
let requestString = String(format:"%#", webservice) as String
//Covert URL request string to URL
guard let url = URL(string: requestString) else {
print("Error: cannot create URL")
return
}
//Convert URL to URLRequest
let urlRequest = URLRequest(url: url)
print(urlRequest)
//Add the username and password to URLCredential
credentials = URLCredential(user:username, password:password, persistence: .forSession)
print(credentials)
//Setup the URLSessionConfiguration
let config = URLSessionConfiguration.default
//Setup the URLSession
let session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
//Prepare the task to get data.
let task = session.dataTask(with: urlRequest, completionHandler: { (data, response, error) in
DispatchQueue.main.async(execute: {
print(error!)
if(error == nil)
{
completion(true)
}
else
{
completion(false)
}
})
})
//Run the task to get data.
task.resume()
}
In the Apple documentation for URLProtectionSpace.serverTrust, it says:
nil if the authentication method of the protection space is not server trust.
So, you are trying to unwrap an optional value of nil, which of course would cause a crash.
In your case, you could probably just replace the entire function with (untested):
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void){
var disposition: URLSession.AuthChallengeDisposition = URLSession.AuthChallengeDisposition.performDefaultHandling
print(challenge.protectionSpace.authenticationMethod)
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
disposition = URLSession.AuthChallengeDisposition.performDefaultHandling
}
else
{
disposition = URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge
}
completionHandler(disposition, credential);
}
I have this class here and inside the class is a method and I am trying to do an NSURLSession on an API that requires windows authentication username and password. I have followed the tutorial here https://gist.github.com/n8armstrong/5c5c828f1b82b0315e24
and came up with this:
let webservice = "https://api.com"
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let urlSession = NSURLSession(configuration: config)
class WebService: NSObject {
func loginUser(username: String, password: String) -> Bool {
let userPasswordString = "username#domain.com:Password"
let userPasswordData = userPasswordString.dataUsingEncoding(NSUTF8StringEncoding)
let base64EncodedCredential = userPasswordData!.base64EncodedStringWithOptions([])
let authString = "Basic \(base64EncodedCredential)"
config.HTTPAdditionalHeaders = ["Authorization" : authString]
let requestString = NSString(format:"%#", webservice) as String
let url: NSURL! = NSURL(string: requestString)
let task = urlSession.dataTaskWithURL(url) {
(let data, let response, let error) in
if (response as? NSHTTPURLResponse) != nil {
let dataString = NSString(data: data!, encoding: NSUTF8StringEncoding)
print(dataString)
}
}
task.resume()
return true
}
}
but when I run this I get a 401 error: 401 - Unauthorized: Access is denied due to invalid credentials.
I have confirmed the URL to the API is correct. Same with the username and password. What am I doing wrong?
I was able to fix this by doing the following:
var credential: NSURLCredential!
func loginUser(username: String, password: String) -> Bool {
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)
credential = NSURLCredential(user:username, password:password, persistence: .ForSession)
let requestString = NSString(format:"%#", webservice) as String
let url: NSURL! = NSURL(string: requestString)
let task = session.dataTaskWithURL(url, completionHandler: {
data, response, error in
dispatch_async(dispatch_get_main_queue(),
{
if(error == nil)
{
print("Yay!")
}
else
{
print("Naw!")
}
})
})
task.resume()
return true
}
and then adding NSURLSessionDelegate methods:
func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
if challenge.previousFailureCount > 0
{
completionHandler(NSURLSessionAuthChallengeDisposition.CancelAuthenticationChallenge, nil)
}
else
{
completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, NSURLCredential(forTrust:challenge.protectionSpace.serverTrust!))
}
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential,credential)
}
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