How do I make task wait for completion in Swift 3 - swift3

I'm using Alamofire to send a login request to an HTTP server. It returns a JSON response. My problem is that the mainline code finishes before the .responseJSON. How do I wait until the response is returned before returning from the function?
var ret: Bool = true
Alamofire.request(
URL(string: "http://localhost:8081/login/iPhone")!,
method: .post,
parameters: ["email":"test#test.test", "password":"test", "uuid":String(describing: UIDevice.current.identifierForVendor!.uuidString)],
headers: [:])
.validate()
.responseJSON{(response) -> Void in
do {
guard response.result.isSuccess else {
throw FieldError.fetchError(responseError: response.result.error)
}
guard let value = response.result.value as? [String: Any],
let status = value["status"] as? String,
let message = value["message"] as? String else {
throw FieldError.messageFormatError
}
switch status {
case "Login suggess":
break
default:
throw FieldError.fieldServerError(status: status, message: message)
}
} catch {
ret = false
debugPrint(error)
}
}
return ret

You can always use global
typealias DownloadComplete = () -> ()
next in func with you're json
func yourFunc(completed: #escaping DownloadComplete)
and after download use
completed()

Related

SwiftUI - Publish Background Thread Not Allowed - on code that does not update ui

New to swiftui and don't understand why the JSONDecoder() line in the first code throws
[SwiftUI] Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.
This to me is not updating ui so why is this showing?
do {
// pass the request type, bearer token, and email / password ( which are sent as POST body params )
let request = L.getRequest(requestType:"POST", token: token, email: self.username, password: self.psw)
L.fetchData(from: request) { result in
switch result {
case .success(let data):
// covert the binary data to a swift dictionary
do {
let response = try JSONDecoder().decode(WpJson.self, from: data)
for (key, title) in response.allowedReadings {
let vimeoId = Int( key )!
let vimeoUri = self.buildVimeoUri(vimeoId: key)
self.addReadingEntity(vimeoUri: vimeoUri, vimeoId: vimeoId, title: title)
}
self.writeToKeychain(jwt:response.jwt, displayName: response.displayName)
readings = self.fetchReadings()
}
catch {
self.error = error.localizedDescription
}
case .failure(let error):
self.error = error.localizedDescription
}
}
}
I tried wrapping a main queue around the do-catch in the L.fetchData(from: request) { result in but this did not help
DispatchQueue.main.async { [weak self] in
Here is the Login protocol, again without any ui work:
import Foundation
import SwiftUI
struct Login: Endpoint {
var url: URL?
init(url: URL?) {
self.url = url
}
}
protocol Endpoint {
var url: URL? { get set }
init(url: URL?)
}
extension Endpoint {
func getRequestUrl() -> URLRequest {
guard let requestUrl = url else { fatalError() }
// Prepare URL Request Object
return URLRequest(url: requestUrl)
}
func getRequest(requestType:String="POST", token:String, email:String="", password:String="") -> URLRequest {
var request = self.getRequestUrl()
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
if ( "" != email && "" != password && requestType == "POST") {
let parameters:[String:String?] = [
"email": email,
"password": password
]
// Run the request
do {
// pass dictionary to nsdata object and set it as request body
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
} catch let error {
print(error.localizedDescription)
}
}
return request;
}
func fetchData(from request: URLRequest, completion: #escaping (Result<Data, NetworkError>) -> Void) {
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
completion(.success(data))
} else if error != nil {
// any sort of network failure
completion(.failure(.requestFailed))
} else {
// this ought not to be possible, yet here we are
completion(.failure(.unknown))
}
}.resume()
}
}
extension URLSession {
func dataTask(with request: URLRequest, completionHandler: #escaping (Result<(Data, HTTPURLResponse), Error>) -> Void) -> URLSessionDataTask {
return dataTask(with: request, completionHandler: { (data, urlResponse, error) in
if let error = error {
completionHandler(.failure(error))
} else if let data = data, let urlResponse = urlResponse as? HTTPURLResponse {
completionHandler(.success((data, urlResponse)))
}
})
}
}
Do you have any idea on how to fix this?
Wrap it right in place of assignment
catch {
DispatchQueue.main.async {
self.error = error.localizedDescription
}
}
case .failure(let error):
DispatchQueue.main.async {
self.error = error.localizedDescription
}
}

Migrating Alamofire.Request extension from Swift 2 to Swift 3

I'm trying to migrate an extension to Alamofire.Request but am getting the error Cannot call value of non-function type 'HTTPURLResponse?'.
I know the compiler thinks I'm referring to the member response and not the function.
I've already replaced Request.JSONResponseSerializer with DataRequest.jsonResponseSerializer.
Can anyone see what I'm missing?
extension Alamofire.Request {
public func responseSwiftyJSON(_ queue: DispatchQueue? = nil, options: JSONSerialization.ReadingOptions = .allowFragments, completionHandler: #escaping (NSURLRequest, HTTPURLResponse?, SwiftyJSON.JSON, Error?) -> Void) -> Self {
return response(responseSerializer: Alamofire.DataRequest.jsonResponseSerializer(options: options)) { response in
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
var responseJSON: JSON
if response.result.isFailure {
responseJSON = JSON.null
} else {
responseJSON = SwiftyJSON.JSON(response.result.value!)
}
dispatch_async(queue ?? dispatch_get_main_queue(), {
completionHandler(self.request!, self.response, responseJSON, response.result.error)
})
}
}
}
}
I'm not sure if this is compatible with older versions, but I suggest you rewrite your extensions as this:
extension DataRequest {
public func responseSwiftyJSON(queue: DispatchQueue? = nil, options: JSONSerialization.ReadingOptions = .allowFragments, completionHandler: #escaping (URLRequest?, HTTPURLResponse?, SwiftyJSON.JSON, Error?) -> Void) -> Self {
return responseJSON(queue: queue, options: options) {
let responseJSON: JSON
switch result {
case let .success(json): responseJSON = JSON(json)
case .failure: responseJSON = JSON.null
}
completionHandler(request, response, responseJSON, error)
}
}
}

Json parsing in Swift 3.0

This is my code for Jason parsing in Swift:
static func POST(url: String, parameters: NSDictionary, completionBlock: #escaping CompletionBlock){
let todoEndpoint: String = Webservices.Base_Url.appending(url)
guard let url = NSURL(string: todoEndpoint) else {
print("Error: cannot create URL")
return
}
var request = URLRequest(url: url as URL)
//var request = URLRequest(url: NSURL(string: todosEndpoint)! as URL)
let session = URLSession.shared
request.httpMethod = "POST"
var err: NSError?
let jsonData = try? JSONSerialization.data(withJSONObject: parameters)
request.httpBody = jsonData
request.addValue("application/x-www-form-urlencoded;charset=UTF-8 ", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
let task = session.dataTask(with: request, completionHandler: {data, response, error -> Void in
guard error == nil else {
print("error calling POST on /todos/1")
print(error)
return
}
// make sure we got data
guard let dataTemp = data else {
print("Error: did not receive data")
return
}
// parse the result as JSON, since that's what the API provides
do {
guard let todo = try JSONSerialization.jsonObject(with: dataTemp, options: []) as? [String: AnyObject] else {
print("error trying to convert data to JSON")
return
}
// now we have the todo, let's just print it to prove we can access it
print("The todo is: " , todo)
// the todo object is a dictionary
// so we just access the title using the "title" key
// so check for a title and print it if we have one
} catch {
print("error trying to convert data to JSON")
return
}
})
task.resume()
}
I got while jason parsing:
error expression produced error: error: Execution was interrupted,
reason: EXC_BAD_ACCESS (code=1, address=0x0). The process has been
returned to the state before expression evaluation.
What's wrong?

How do I know when Data finished downloading

I would like to know when the function below has finished downloading the data.
I know I can use a completion handler, but this is in an NSObject and I'd like to know when to return the completion handler for this function, making sure the video is downloaded completely and ready to go, before the function returns the completion handler, and my View Controller resumes it's logic.
Thanks :)
func downloadVideo(identity:JSON_Video){
// use guard to make sure you have a valid url
let videoId = identity.video_id!
let videoString:String = "\(Constants.endPoint_video)\(videoId).mp4"
guard let videoURL = URL(string: videoString) else { return }
let library_url = self.findVideo(video: identity)
if self.findVideo(video: identity) != nil{
print("Video exists. No need to download")
print("Existing video \(library_url!)")
}else{
print("Video not found. Downloading now")
// Variables to input on request
let loginString = Constants.loginString
let loginData = loginString.data(using: String.Encoding.utf8)!
let base64LoginString = loginData.base64EncodedString()
// URL request
var urlRequest = URLRequest(url: videoURL)
urlRequest.httpMethod = "GET"
urlRequest.setValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
// set up the session
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
// make the request
let task = session.dataTask(with: urlRequest, completionHandler: { (data, response, error) in
// do stuff with response, data & error here
if let error = error{
print("Data Session Error: \(error.localizedDescription)")
print(response ?? "No response")
return
}
if let data = data {
print("Incoming video....")
print("Data: \(data.description)")
if let finalDatabaseURL = self.videosBaseUrl()?.appendingPathComponent("\(identity.video_id!).mp4"){
print("Copying from: \(videoURL)")
print("Copying to: \(finalDatabaseURL)")
do {
try
data.write(to: finalDatabaseURL)
print("Writing data to file")
// *****
// How do I know when data finished writing?
// Completion handler goes here ?
// *****
}catch{
print("Error writing data to file")
print(error.localizedDescription)
}
}
}
}
})
task.resume()
}
}

Swift 3: Alamorefire block UI

I have APIManager singleton class and have a function to get data from server like this:
func scanOrder(order: String, completion:#escaping Handler){
let url = K.API_URL + "/api/containers/picking/" + order
Alamofire.request(url, method: .get, parameters: nil, encoding: URLEncoding.default, headers: getHeader()).responseJSON { (response) in
DispatchQueue.main.async {
completion(response)
}
}
}
and I call this function in other class like this:
apiMan.scanOrder(order: tfCode.text!) { (response) in
...
}
while waiting for server to response, my UI is blocked. I tried to wrap alamofire request call within DispatchQueue.global().async but it still blocks the UI.
Please help!
I never used Alamofire.request with DispatchQueue.main.async like you do. The reason is that Alamofire in combination with completion blocks already operates async and shouldn't block the UI, which is settled in the Main Thread.
Have you tried something like:
class NetworkManager {
func scanOrder(order: String, completion:#escaping (Any?) -> Void){
let url = "https://example.com/api/containers/picking/" + order
Alamofire.request(url, method: .get, parameters: nil, encoding: URLEncoding.default, headers: AppConfiguration.sharedInstance.defaultHeader())
.responseJSON { response in
guard response.result.isSuccess else {
Log.info("Error while fetching: \(response.result.error)")
completion(nil)
return
}
guard let responseJSON = response.result.value as? [String: AnyObject] else {
Log.info("Invalid information received from service")
completion(nil)
return
}
completion(responseJSON)
}
}
}
Call:
class CallingClass {
func scanOrder(order:String){
let manager = NetworkManager()
var result: Any?
manager.scanOrder(order: "example") { response in
result = response
}
print(result as Any)
}
}