Completion gets called soon - swift

I have the following functions. I'm trying to pass allItems array into the completion block of requestItems, but I get a crash as it says it's nil. I removed the completion to check that item has a value and it does.
That means that the completion executes before the for loop.
Is there another approach for this? Something like Promises in Javascript that will execute the completion when the for loop has finished.
func requestItems(_ data: [String: Any], completion: (Bool, [Item]) -> Void) {
var allItems = [Item]()
for i in data["all"] {
Routes.instance.getRequest(requestType: "items", params: nil, id: someId, completion: { item in
var it = Item(item["name"] as! String)
allItems.append(it)
})
}
completion(true, allItems)
}
func getRoutes(requestType: String, parameters: [String: Any]?, id: String, completion: #escaping ([[String:Any]]) -> Void) {
DispatchQueue.main.async {
if id == "" {
self.url = "\(URL_BASE)/\(requestType)"
} else {
self.url = "\(URL_BASE)/\(requestType)/\(id)"
}
Alamofire.request(self.url, method: .get, parameters: parameters, encoding: JSONEncoding.default, headers: self.headers).responseJSON { response in
guard response.result.error == nil else {
print(response.result.error!)
return
}
switch response.result {
case .success(let JSON):
let response = [JSON] as! NSArray
for item in response {
if let data = item as? [String: Any] {
print(data)
}
}
completion(response as! [[String : Any]])
case .failure(let error):
print("Request failed with error: \(error)")
}
}
}
}
The completion handler executes too soon, returning a nil item

You need DispatchGroup to get notified when the asynchronous loop is finished for example:
func requestItems(_ data: [String: Any], completion: (Bool, [Item]) -> Void) {
var allItems = [Item]()
let group = DispatchGroup()
for i in data["all"] {
group.enter()
Routes.instance.getRequest(requestType: "items", params: nil, id: someId, completion: { item in
let it = Item(item["name"] as! String)
allItems.append(it)
group.leave()
})
}
group.notify(queue: DispatchQueue.main) {
completion(true, allItems)
}
}

You are calling your completion handler outside the completion handler of the asynchronous request getRequest, so it will obviously return before the function would finish execution. Since you are calling several asynchronous requests in a row, a simple completion handler won't do the trick.
The best approach is to either use a DispatchQueue to only let your function return when all the requests are finished or to use a 3rd party framework, such as PromiseKit to handle the async functions are normal functions with return values.

Related

Swift generics in completion handler

Im trying to refactor dat fetching func to enable it for several Decodable struct types.
func fetchData<T: Decodable>(_ fetchRequest: FetchRequestType, completion: #escaping ((Result<T, Error>) -> Void)) {
guard !isFetching else { return }
isFetching = true
guard let url = getURL(fetchRequest) else { assertionFailure("Could not compose URL"); return }
let urlRequest = URLRequest(url: url)
self.session.dataTask(with: urlRequest) { [unowned self] (data, response, error) in
guard let response = response as? HTTPURLResponse,
response.statusCode == 200 else {
self.isFetching = false
completion(.failure(NSError()))
return
}
guard let data = data else { assertionFailure("No data"); return }
if let jsonData = try? JSONDecoder().decode(T.self, from: data) {
self.isFetching = false
completion(.success(jsonData))
} else {
assertionFailure("Could not decode JSON data"); return
}
}.resume()
}
But when Im calling the func from controller with one of Decodable types I get a compile error
Generic parameter 'T' could not be inferred
networkClient.fetchData(.accountsSearch(searchLogin: text, pageNumber: 1)) { [unowned self] result in
switch result {
case .success(let dataJSON):
let accountsListJSON = dataJSON as! AccountsListJSON
let fetchedAccounts = accountsListJSON.items
.map({ AccountGeneral(login: $0.login, id: $0.id, avatarURLString: $0.avatarURL, type: $0.type) })
self.accounts = fetchedAccounts
case .failure(_):
assertionFailure("Fetching error!")
}
}
Please help me to find out what happened and solve a problem.
You can generally help the compiler to infer the T type by providing the result type, when you call fetchData(_:completion:) function like this:
networkClient.fetchData(
.accountsSearch(searchLogin: text, pageNumber: 1)
) { [unowned self] (result: Result<AccountsListJSON, Error>) in
...
}
If the method doesn't have a return type where the static type can be specified you have to add a parameter
func fetchData<T: Decodable>(_ fetchRequest: FetchRequestType, type: T.Type, completion: #escaping (Result<T, Error>) -> Void) { ...
and call it
networkClient.fetchData(.accountsSearch(searchLogin: text, pageNumber: 1), type: AccountsListJSON.self) { [unowned self] result in
and delete the downcast as! AccountsListJSON

Parallel URLSession requests w/ DispatchGroup call completion handler twice on 1 request

Using DispatchGroup I am trying to run 2 network requests against my client, returning the results when both have completed.
I am having an issue in that sometimes the completion handler for one of DispatchGroup requests is called twice and the other is not called at all.
An example would be -
func fetchProfileWithRelatedArticle(onSuccess: #escaping (User, [RelatedArticle]) -> Void, onError: #escaping (Error) -> Void) {
let dispatchGroup = DispatchGroup()
var user: User?
var articles: [RelatedArticle] = []
var errors: [Error] = []
dispatchGroup.enter()
fetchProfileForUser(onSuccess: {
user = $0
print("fetchProfile:",$0)
print("123")
dispatchGroup.leave()
}, onError: { error in
errors.append(error)
dispatchGroup.leave()
})
dispatchGroup.enter()
getArticlesForUser(onSuccess: {
articles = $0
print("getArticlesForUser:",$0)
print("456")
dispatchGroup.leave()
}, onError: { error in
errors.append(error)
dispatchGroup.leave()
})
dispatchGroup.notify(queue: .main) {
guard let user = user, errors.isEmpty else { return }
onSuccess(user, articles)
}
}
Here I fetch a user profile and also fetch a list of articles they have written. These are returned via a completion handler and presented elsewhere.
Most of the time this works, however it appears on occasion either one of those requests will call its own completion handler twice and the other request wont.
I suspect this may be down to when my access token expires as it occurs if I leave the app for a short time. My access token has a life of 2 minutes.
Should a request receive a 401 response, I have the following method in my network client that requests a new token, then invokes the call again. I believe this may not be working as I'd like.
if response.statusIs401() {
self?.refreshHandler { success in
guard success else { completion(.failure(TokenError.refused)); return }
self?.request(resource, completion)
}
return
}
I suspect calling the method again after the update is doing something to the requests my dispatch group is returning.
Is it possible to chain requests in this fashion?
struct NoContent: Codable { }
typealias RefreshHandler = (#escaping (Bool) -> Void) -> ()
typealias TokenGetter = () -> [String: String]
protocol ClientType: class {
associatedtype Route: RouterType
func request<T: Codable>(_ resource: Route, _ completion: #escaping (Result<T>)-> Void)
}
class Client<Route: RouterType>: ClientType {
enum APIError: Error {
case unknown, badResponse, jsonDecoder, other
}
enum TokenError: String, Error {
case expired = "Access Token Expired"
case refused = "Refresh Token Failed"
}
private(set) var session: SessionType
private(set) var tokenGetter: TokenGetter
private(set) var refreshHandler: RefreshHandler
private lazy var decoder: JSONDecoder = {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601withFractionalSeconds
return decoder
}()
init(session: SessionType, tokenGetter: #escaping TokenGetter, refreshHandler: #escaping RefreshHandler) {
self.session = session
self.tokenGetter = tokenGetter
self.refreshHandler = refreshHandler
}
func request<T: Codable>(_ resource: Route, _ completion: #escaping (Result<T>)-> Void) {
let request = URLRequest(
resource: resource,
headers: tokenGetter()
)
URLSession.shared.dataTask(with: request) { [weak self] data, response, error in
guard error == nil else { completion(.failure(APIError.unknown)); return }
guard let response = response as? HTTPURLResponse else { completion(.failure(APIError.badResponse)); return }
if response.statusIs401() {
self?.refreshHandler { success in
guard success else { completion(.failure(TokenError.refused)); return }
self?.request(resource, completion)
}
return
}
if response.statusIsSuccess() {
guard let self = self, let data = self.deserializeNoContentResponse(data: data) else { completion(.failure(APIError.badResponse)); return }
do {
let value = try self.decoder.decode(T.self, from: data)
DispatchQueue.main.async {
completion(.success(value))
}
} catch let error {
print(error)
}
return
}
completion(.failure(APIError.other))
}.resume()
}
// some calls return a 200/201 with no data
private func deserializeNoContentResponse(data: Data?) -> Data? {
if data?.count == 0 {
return "{ }".data(using: .utf8)
}
return data
}
}
Sounds like you would need to something like the following in your networking client:
func makeTheRequest(_ completion: CompletionHandler) {
URLSession.shared.dataTask(with: someURL) { data, response, error in
guard let httpResponse = response as? HTTPURLResponse else {
return
}
if httpResponse.statusCode == 401 {
self.refreshToken { success in
if success { self.makeTheRequest(completion) }
}
}
// handle response
completion(whateverDataYouNeedToPass)
}
}
That would make the call, check the response code, refresh the token is needed and if that succeeds it calls the method that should make the response again with the completion handler that was passed to in the first place without calling it first. So the completion handler wouldn't be called until after the API call is made the second time.
Of course, adopt this for your own code, shouldn't be too hard to do

Where do Dispatch Group commands go in code?

I am trying to run a function X times in a for in loop but when all the functions have returned I want to run another function.
Currently I have it working by delaying the final function 1 second but I would really like to get Dispatch Group working.
I've been through various online examples and other questions but nothing I try seems to work, The code I have at the moment I know won't work as it is running dispatchGroup.leave() each time the for in functions are sent rather than when they return.
I've tried puting the DispatchGroup code in the function (which is in another file) but I'm stumped, however I think I am close to a solution.
I've also looked at semaphores and using count and incrementing a value each time the loop runs but I keep coming back to DispatchGroups.
My last resort is to ask a question!
ViewController code
#IBAction func removeDeviceBtn(_ sender: Any) {
let dispatchGroup = DispatchGroup()
for owner in arrOwnerList {
dispatchGroup.enter()
self.removeDevice(device: self.device, account: owner as! String, completion: self.completed)
dispatchGroup.leave()
}
dispatchGroup.notify(queue: DispatchQueue.main, execute: {
self.removeDeviceFromServer(device: self.device)
self.sendEmail(to:"gordon#example.co.uk", subject:self.device+" has been removed", text:self.device+" has been removed from the server, please check the sim for bar and termination")
})
Function code in other file as an extension
func completed(isSuccess: Bool) {
}
func removeDevice(device: String, account: String, completion: #escaping (Bool) -> Void) {
let dictHeader : [String:String] = ["username":Username,"password":Password]
let dictArray = [device]
WebHelper.requestPUTAPIRemoveDevice(BaseURL+"rootaccount/removedevices/"+account+"?server=MUIR", header: dictHeader, dictArray: dictArray, controllerView: self, success: { (response) in
if response.count == 0 {
DispatchQueue.main.async {
GlobalConstant.showAlertMessage(withOkButtonAndTitle: GlobalConstant.AppName, andMessage: Messages.ServerError, on: self)
}
}
else {
if response.count != 0 {
let isSuccess = true
completion(isSuccess)
}
else{
DispatchQueue.main.async {
GlobalConstant.showAlertMessage(withOkButtonAndTitle: GlobalConstant.AppName, andMessage: Messages.NoDataFound, on: self)
}
}
}
}) { (error) in
DispatchQueue.main.async {
GlobalConstant.showAlertMessage(withOkButtonAndTitle: GlobalConstant.AppName, andMessage: error?.localizedDescription ?? Messages.ServerError, on: self)
}
}
}
Code from WebHelper file
class func requestPUTAPIRemoveDevice(_ strURL: String,header: Dictionary<String,String>,dictArray: Array<Any>, controllerView viewController: UIViewController, success: #escaping (_ response: [AnyHashable: Any]) -> Void, failure: #escaping (_ error: Error?) -> Void) {
if GlobalConstant.isReachable() {
DispatchQueue.main.async {
LoadingIndicatorView.sharedInstance.showHUD()
}
let loginString = String(format: "%#:%#", header["username"]!, header["password"]!)
let loginData: Data = loginString.data(using: String.Encoding.utf8)!
let base64LoginString = loginData.base64EncodedString(options: NSData.Base64EncodingOptions())
let headers = ["Authorization": "Basic "+base64LoginString, "Referer": "http://www.example.com"]
let postData = try? JSONSerialization.data(withJSONObject: dictArray, options: [])
let request = NSMutableURLRequest(url: NSURL(string: strURL)! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
request.httpMethod = "PUT"
request.allHTTPHeaderFields = headers
request.httpBody = postData
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
if (error != nil) {
DispatchQueue.main.async {
LoadingIndicatorView.sharedInstance.hideHUD()
}
failure(error)
} else {
if let httpResponse = response as? HTTPURLResponse {
print("Server code \(httpResponse.statusCode)")
if httpResponse.statusCode == 200 || httpResponse.statusCode == 208 {
DispatchQueue.main.async {
LoadingIndicatorView.sharedInstance.hideHUD()
}
let jsonResult = try? JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers)
if (jsonResult is NSDictionary) {
success(jsonResult as! [AnyHashable : Any])
}
else if (jsonResult is NSArray) {
success(["response":jsonResult as! NSArray])
}
else{
success(["response":httpResponse.statusCode])
DispatchQueue.main.async {
}
}
}
else{
DispatchQueue.main.async {
LoadingIndicatorView.sharedInstance.hideHUD()
}
failure(error)
}
}
}
})
dataTask.resume()
}
else {
DispatchQueue.main.async {
LoadingIndicatorView.sharedInstance.hideHUD()
GlobalConstant.showAlertMessage(withOkButtonAndTitle: "", andMessage: "Internet not connected", on: viewController)
}
}
}
The final solution (apart from tidying up the various other issues) was to add success(["response":httpResponse.statusCode]) to the WebHelper file, corrected code above
Put the leave inside the completion handler:
for owner in arrOwnerList {
dispatchGroup.enter()
removeDevice(device: device, account: owner as! String) { [weak self] success in
self?.completed(isSuccess: success)
dispatchGroup.leave()
}
}
Or given that you’re not really doing anything in completed function, I’d just remove that:
for owner in arrOwnerList {
dispatchGroup.enter()
removeDevice(device: device, account: owner as! String) { _ in
dispatchGroup.leave()
}
}
I notice that you have paths of execution in removeDevice that aren’t calling the completion handler. Make sure every path of execution calls the completion handler or else your dispatch group will never get resolved.
func removeDevice(device: String, account: String, completion: #escaping (Bool) -> Void) {
let dictHeader = ["username": Username, "password": Password]
let dictArray = [device]
WebHelper.requestPUTAPIRemoveDevice(BaseURL+"rootaccount/removedevices/"+account+"?server=MUIR", header: dictHeader, dictArray: dictArray, controllerView: self, success: { response in
DispatchQueue.main.async {
if response.count == 0 {
GlobalConstant.showAlertMessage(withOkButtonAndTitle: GlobalConstant.AppName, andMessage: Messages.ServerError, on: self)
completion(false)
} else {
completion(true)
}
}
}, failure: { error in
DispatchQueue.main.async {
GlobalConstant.showAlertMessage(withOkButtonAndTitle: GlobalConstant.AppName, andMessage: error?.localizedDescription ?? Messages.ServerError, on: self)
completion(false)
}
})
}
By the way, I don’t know the name of the “failure” closure, so I assumed it was failure, but adjust as required by your requestPUTAPIRemoveDevice method. We generally avoid the multiple closure pattern in Swift, but if you’re going to do that, I’d avoid the trailing closure syntax. It makes the functional intent of this second closure a bit more explicit.
Or, this all begs the question as to why requestPUTAPIRemoveDevice is initiating UI updates at all. I’d probably put that in the view controller method. So requestPUTAPIRemoveDevice should just pass back enough information so the removeDeviceBtn routines knows what error to present. And this idea of presenting a separate error message for each failure is probably suspect, too. (E.g. if you have lost internet connection and are trying to remove a dozen devices, do you really want to show a dozen separate error messages?) But this is beyond the scope of this question.

DispatchQueue does not update the data in swift

I'm need to do a search operation in Swift and me using UISearchbar for it.
On textDidChange event, I need to call a web api, parse the response and then update the array and then begin the search on updated array.
But not sure my code does not work.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
let group = DispatchGroup()
group.enter()
// Perform some asynchronous operation
let queue1 = DispatchQueue(label: "abc")
queue1.async {
self.callWebAPI() // This function calls the web api and parses it’s response
group.leave()
}
DispatchQueue.global(qos: .utility).async {
DispatchQueue.main.async {
self.filteredCountry = self.arrCountry.filter({$0.name.prefix(searchText.count) == searchText})
self.searching = true
self.tableView.reloadData()
}
}
}
func callWebAPI() {
let urlString = URL(string: "https://restcountries.eu/rest/v2/all")
if let url = urlString {
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
} else {
if let usableData = data {
do{
//here dataResponse received from a network request
let jsonResponse = try JSONSerialization.jsonObject(with:
data!, options: [])
print(jsonResponse) //Response result
guard let jsonArray = jsonResponse as? [[String: Any]] else {
return
}
print(jsonArray)
print("done")
} catch let parsingError {
print("Error", parsingError)
}
}
}
}
task.resume()
}
}
Please guide on my above code as not sure where I'm wrong
The issue is that callWebAPI is asynchronous (it returns immediately before the request is done), so you are calling leave immediately. You could give this method a completion handler and call leave in that. And you would also call the UI update in a notify block for your group, not just dispatch it.
Easier, just retire the DispatchGroup entirely and just update your UI in the completion handler you supply to callWebAPI.
For example, give callWebAPI a completion handler:
func callWebAPI(completionHandler: #escaping ([[String: Any]]?, Error?) -> Void) {
let urlString = URL(string: "https://restcountries.eu/rest/v2/all")
if let url = urlString {
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data, error == nil else {
completionHandler(nil, error)
return
}
do {
let jsonResponse = try JSONSerialization.jsonObject(with:
data)
completionHandler(jsonResponse as? [[String: Any]], nil)
} catch let parsingError {
completionHandler(nil, parsingError)
}
}
task.resume()
}
}
And then, you can eliminate the dispatch groups, the global queues (because it’s already an asynchronous method, you don’t need to invoke this from background queue), etc., and it’s simplified to just:
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
callWebAPI { jsonResponse, error in
guard let jsonResponse = jsonResponse, error == nil else {
print("Error:", error ?? "Response was not correct format")
return
}
print(jsonResponse)
// Note, you don’t appear to be using `jsonResponse` at all,
// so I presume you’d update the relevant model objects.
DispatchQueue.main.async {
self.filteredCountry = self.arrCountry.filter({$0.name.prefix(searchText.count) == searchText})
self.searching = true
self.tableView.reloadData()
}
}
}
As an aside, nowadays we use JSONDecoder to parse JSON to populate model objects directly, but that’s beyond the scope of this question.

swift class variable changing in same scope [duplicate]

I am making url calls thru an API that I created using swift as follows:
class API {
let apiEndPoint = "endpoint"
let apiUrl:String!
let consumerKey:String!
let consumerSecret:String!
var returnData = [:]
init(){
self.apiUrl = "https://myurl.com/"
self.consumerKey = "my consumer key"
self.consumerSecret = "my consumer secret"
}
func getOrders() -> NSDictionary{
return makeCall("orders")
}
func makeCall(section:String) -> NSDictionary{
let params = ["consumer_key":"key", "consumer_secret":"secret"]
Alamofire.request(.GET, "\(self.apiUrl)/\(self.apiEndPoint + section)", parameters: params)
.authenticate(user: self.consumerKey, password: self.consumerSecret)
.responseJSON { (request, response, data, error) -> Void in
println("error \(request)")
self.returnData = data! as NSDictionary
}
return self.returnData
}
}
I call this API in my UITableViewController to populate the table with SwiftyJSON library. However my returnData from the API is always empty. There is no problem with Alomofire calls as I can successfully retrieve value. My problem is how I am supposed to carry this data over to my table view controller?
var api = API()
api.getOrders()
println(api.returnData) // returnData is empty
As mattt points out, Alamofire is returning data asynchronously via a “completion handler” pattern, so you must do the same. You cannot just return the value immediately, but you instead want to change your method to not return anything, but instead use a completion handler closure pattern.
Nowadays, that might look like:
func getOrders(completionHandler: #escaping (Result<[String: Any]>) -> Void) {
performRequest("orders", completion: completionHandler)
}
func performRequest(_ section: String, completion: #escaping (Result<[String: Any]>) -> Void) {
let url = baseURL.appendingPathComponent(section)
let params = ["consumer_key": "key", "consumer_secret": "secret"]
Alamofire.request(url, parameters: params)
.authenticate(user: consumerKey, password: consumerSecret)
.responseJSON { response in
switch response.result {
case .success(let value as [String: Any]):
completion(.success(value))
case .failure(let error):
completion(.failure(error))
default:
fatalError("received non-dictionary JSON response")
}
}
}
Then, when you want to call it, you use this completion closure parameter (in trailing closure, if you want):
api.getOrders { result in
switch result {
case .failure(let error):
print(error)
case .success(let value):
// use `value` here
}
}
// but don't try to use the `error` or `value`, as the above closure
// has not yet been called
//
From the Alamofire README (emphasis added):
Networking in Alamofire is done asynchronously. Asynchronous programming may be a source of frustration to programmers unfamiliar with the concept, but there are very good reasons for doing it this way.
Rather than blocking execution to wait for a response from the server, a callback is specified to handle the response once it's received. The result of a request is only available inside the scope of a response handler. Any execution contingent on the response or data received from the server must be done within a handler.
Following is the complete flow for performing the 'Login Action' using Alamofire and Swift.
Alamofire v3.3
Swift 2.2
Xcode 7.3
I have used GCD and MBProgressHUD for my own convenience. Refactor and use as you like :)
func loginBtnTapped(sender: AnyObject) {
MBProgressHUD.showHUDAddedTo(self.view, animated: true)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
let loginInfo : Dictionary<String,AnyObject> = ["email":"abc#g.com","password":"abc123"]
self.loginUser(loginInfo) { responseObject, error in
print("\(responseObject) \n \(error) ")
// Parsing JSON Below
let status = Int(responseObject?.objectForKey("status") as! String)
if status == 1 {
// Login Successfull...Move To New VC
}
else {
print(responseObject?.objectForKey("message"))! as! String)
}
return
}
dispatch_async(dispatch_get_main_queue()) {
MBProgressHUD.hideHUDForView(self.view, animated: true)
}
}
}
func loginUser(parameters:NSDictionary, completionHandler: (NSDictionary?, NSError?) -> ()) {
self.postRequest("http://qa.company.com/project/index.php/user/login",
paramDict: parameters as? Dictionary<String, AnyObject>,
completionHandler: completionHandler)
}
func postRequest(urlString: String, paramDict:Dictionary<String, AnyObject>? = nil,
completionHandler: (NSDictionary?, NSError?) -> ()) {
Alamofire.request(.POST, urlString, parameters: paramDict)
.responseJSON { response in
switch response.result {
case .Success(let JSON):
completionHandler(JSON as? NSDictionary, nil)
case .Failure(let error):
completionHandler(nil, error)
}
}
}
Details
xCode 9.1, Swift 4
Features:
Easy readable code
Ready templates (it's easy to add more requests)
Embedded solution with asynchronous data processing
Full examples
Sample 1
Return data using closure
Data1.searchRequest(term: "jack johnson") { json, error in
print(error ?? "nil")
print(json ?? "nil")
print("Update views")
}
Full sample 1
Data class
import Alamofire
class Data1 {
static fileprivate let queue = DispatchQueue(label: "requests.queue", qos: .utility)
static fileprivate let mainQueue = DispatchQueue.main
fileprivate class func make(request: DataRequest, closure: #escaping (_ json: [String: Any]?, _ error: Error?)->()) {
request.responseJSON(queue: Data1.queue) { response in
// print(response.request ?? "nil") // original URL request
// print(response.response ?? "nil") // HTTP URL response
// print(response.data ?? "nil") // server data
//print(response.result ?? "nil") // result of response serialization
switch response.result {
case .failure(let error):
Data1.mainQueue.async {
closure(nil, error)
}
case .success(let data):
Data1.mainQueue.async {
closure((data as? [String: Any]) ?? [:], nil)
}
}
}
}
class func searchRequest(term: String, closure: #escaping (_ json: [String: Any]?, _ error: Error?)->()) {
let request = Alamofire.request("https://itunes.apple.com/search?term=\(term.replacingOccurrences(of: " ", with: "+"))")
Data1.make(request: request) { json, error in
closure(json, error)
}
}
}
UIViewController
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
Data1.searchRequest(term: "jack johnson") { json, error in
print(error ?? "nil")
print(json ?? "nil")
print("Update views")
}
}
}
Sample 2
Return data using delegate
// ....
var data = Data2()
data.delegate = self
data.searchRequest(term: "jack johnson")
// ....
extension ViewController: Data2Delegate {
func searchRequest(response json: [String : Any]?, error: Error?) {
print(error ?? "nil")
print(json ?? "nil")
print("Update views")
}
}
Full sample 2
Data class
import Alamofire
protocol Data2Delegate: class {
func searchRequest(response json: [String: Any]?, error: Error?)
}
class Data2 {
fileprivate let queue = DispatchQueue(label: "requests.queue", qos: .utility)
fileprivate let mainQueue = DispatchQueue.main
weak var delegate: Data2Delegate?
fileprivate func make(request: DataRequest, closure: #escaping (_ json: [String: Any]?, _ error: Error?)->()) {
request.responseJSON(queue: queue) { response in
// print(response.request ?? "nil") // original URL request
// print(response.response ?? "nil") // HTTP URL response
// print(response.data ?? "nil") // server data
//print(response.result ?? "nil") // result of response serialization
switch response.result {
case .failure(let error):
self.mainQueue.async {
closure(nil, error)
}
case .success(let data):
self.mainQueue.async {
closure((data as? [String: Any]) ?? [:], nil)
}
}
}
}
func searchRequest(term: String) {
let request = Alamofire.request("https://itunes.apple.com/search?term=\(term.replacingOccurrences(of: " ", with: "+"))")
make(request: request) { json, error in
self.delegate?.searchRequest(response: json, error: error)
}
}
}
UIViewController
import UIKit
class ViewController: UIViewController {
private var data = Data2()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
data.delegate = self
data.searchRequest(term: "jack johnson")
}
}
extension ViewController: Data2Delegate {
func searchRequest(response json: [String : Any]?, error: Error?) {
print(error ?? "nil")
print(json ?? "nil")
print("Update views")
}
}
Sample 3
Return data using PromiseKit
_ = data.searchRequest(term: "jack johnson").then { response in
print(response.error ?? "nil")
print(response.json ?? "nil")
print("Update views")
return .void
}
Full sample 3
Data class
import Alamofire
import PromiseKit
class Data3 {
fileprivate let queue = DispatchQueue(label: "requests.queue", qos: .utility)
fileprivate let mainQueue = DispatchQueue.main
fileprivate func make(request: DataRequest) -> Promise<(json:[String: Any]?, error: Error?)> {
return Promise { fulfill, reject in
request.responseJSON(queue: queue) { response in
// print(response.request ?? "nil") // original URL request
// print(response.response ?? "nil") // HTTP URL response
// print(response.data ?? "nil") // server data
//print(response.result ?? "nil") // result of response serialization
switch response.result {
case .failure(let error):
self.mainQueue.async {
fulfill((nil, error))
}
case .success(let data):
self.mainQueue.async {
fulfill(((data as? [String: Any]) ?? [:], nil))
}
}
}
}
}
func searchRequest(term: String) -> Promise<(json:[String: Any]?, error: Error?)> {
let request = Alamofire.request("https://itunes.apple.com/search?term=\(term.replacingOccurrences(of: " ", with: "+"))")
return make(request: request)
}
}
extension AnyPromise {
class var void: AnyPromise {
return AnyPromise(Promise<Void>())
}
}
UIViewController
import UIKit
import PromiseKit
class ViewController: UIViewController {
private var data = Data3()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
_ = data.searchRequest(term: "jack johnson").then { response in
print(response.error ?? "nil")
print(response.json ?? "nil")
print("Update views")
return .void
}
}
}
To parse a json using Swifty JSON, here is how i am doing it.
For #Jenita _Alice4Real
func uploadScans(parameters: [String: AnyObject], completionHandler: (AnyObject?, NSError?) -> ()) {
makePostCall(CommonFunctions().getSaveSKUDataUrl(), parameters: parameters,completionHandler: completionHandler)
}
func makePostCall(url: String, parameters: [String: AnyObject], completionHandler: (AnyObject?, NSError?) -> ()) {
Alamofire.request(.POST, url, parameters: parameters)
.responseJSON { response in
switch response.result {
case .Success(let value):
completionHandler(value, nil)
case .Failure(let error):
completionHandler(nil, error)
}
}
}
uploadScans(params) { responseObject, error in
let json = JSON(responseObject!)
}