I'm using Alamofire in my application and wish to display an alert if the request has an error (e.g. wrong URL), etc.
I have this function in a separate class as it is shared among the pages of the application.
Alamofire.request(.GET, api_url)
.authenticate(user: str_api_username, password: str_api_password)
.validate(statusCode: 200..<300)
.response { (request, response, data, error) in
if (error != nil) {
let alertController = UIAlertController(title: "Server Alert", message: "Could not connect to API!", preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default,handler: nil))
self.presentViewController(alertController, animated: true, completion: nil)
}
}
As Alamofire works asynchronously I need to do the error check then & there (unless you suggest otherwise) because then I want to manipulate the results and if the URL was wrong then it can get messy.
No surprise, the
self.presentViewController(alertController, animated: true, completion: nil)
does not work so how can I display this alert?
I'd say the conventional approach for this is to have whoever calls this network request be responsible for displaying the alert. If when the request is complete, you call back to the original calling object, they are responsible for displaying the alert. One of the reasons for this is that errors can mean different things in different contexts. You may not always want to display an alert - this provides you with more flexibility as you're building out your app. The same way that AlamoFire calls your response closure when it is done, I think it's best to pass that back to whoever initiated this call in your Downloader object.
Update:
You want to structure it the same way AlamoFire structures it. You pass the closure to AF which gets called when the AF request finishes.
You'll have to add a closure param to your download function (See downloadMyStuff). Then, once the AF request finishes, you can call the closure you previously defined ( completion). Here's a quick example
class Downloader {
func downloadMyStuff(completion: (AnyObject?, NSError?) -> Void) {
Alamofire.request(.GET, "http://myapi.com")
.authenticate(user: "johndoe", password: "password")
.validate(statusCode: 200..<300)
.response { (request, response, data, error) in
completion(data, error)
}
}
}
class ViewController: UIViewController {
let downloader = Downloader()
override func viewDidLoad() {
super.viewDidLoad()
self.downloader.downloadMyStuff { (maybeResult, maybeError) -> Void in
if let error = maybeError {
println("Show your alert here from error \(error)")
}
if let result: AnyObject = maybeResult {
println("Parse your result and do something cool")
}
}
}
}
Related
I am trying to create a simple app that fetches a new video from Vimeo everyday. I can access Vimeo and the videos no problem, but the built in "request" function defaults to returning void and I would like to use the video info (URIs, names, .count, etc.) outside of the request function.
The request function returns something called a "RequestToken" which contains a path and a URLSessionDataTask. I thought maybe that was the key but, probably since I'm new to programming, I have been unable to effectively use this info. I've also read a lot of the Vimeo class files associated with VimeoClient and Request and so forth and it seems like it creates a dictionary object when a request has completed but I have no idea how to access that. I feel like this is just some knowledge about functions/closures/returns that I lack and can't quite find the internet search terms to answer.
let videoRequest = Request<[VIMVideo]>(path: "/user/videos")
vimeoClient.request(videoRequest) { result in
switch result {
case .success(let response):
let video: [VIMVideo] = response.model
print("retrieved videos: \(video.count)")
case .failure(let error):
print ("error retrieving video: \(error)")
Here is the full method call under VimeoClient class.
public func request<ModelType>(_ request: Request<ModelType>, completionQueue: DispatchQueue = DispatchQueue.main, completion: #escaping ResultCompletion<Response<ModelType>>.T) -> RequestToken
{
if request.useCache
{
self.responseCache.response(forRequest: request) { result in
switch result
{
case .success(let responseDictionary):
if let responseDictionary = responseDictionary
{
self.handleTaskSuccess(forRequest: request, task: nil, responseObject: responseDictionary, isCachedResponse: true, completionQueue: completionQueue, completion: completion)
}
else
{
let error = NSError(domain: type(of: self).ErrorDomain, code: LocalErrorCode.cachedResponseNotFound.rawValue, userInfo: [NSLocalizedDescriptionKey: "Cached response not found"])
self.handleError(error, request: request)
completionQueue.async {
completion(.failure(error: error))
}
}
case .failure(let error):
self.handleError(error, request: request)
completionQueue.async {
completion(.failure(error: error))
}
}
}
return RequestToken(path: request.path, task: nil)
}
else
{
let success: (URLSessionDataTask, Any?) -> Void = { (task, responseObject) in
DispatchQueue.global(qos: .userInitiated).async {
self.handleTaskSuccess(forRequest: request, task: task, responseObject: responseObject, completionQueue: completionQueue, completion: completion)
}
}
let failure: (URLSessionDataTask?, Error) -> Void = { (task, error) in
DispatchQueue.global(qos: .userInitiated).async {
self.handleTaskFailure(forRequest: request, task: task, error: error as NSError, completionQueue: completionQueue, completion: completion)
}
}
let path = request.path
let parameters = request.parameters
let task: URLSessionDataTask?
switch request.method
{
case .GET:
task = self.sessionManager?.get(path, parameters: parameters, progress: nil, success: success, failure: failure)
case .POST:
task = self.sessionManager?.post(path, parameters: parameters, progress: nil, success: success, failure: failure)
case .PUT:
task = self.sessionManager?.put(path, parameters: parameters, success: success, failure: failure)
case .PATCH:
task = self.sessionManager?.patch(path, parameters: parameters, success: success, failure: failure)
case .DELETE:
task = self.sessionManager?.delete(path, parameters: parameters, success: success, failure: failure)
}
guard let requestTask = task else
{
let description = "Session manager did not return a task"
assertionFailure(description)
let error = NSError(domain: type(of: self).ErrorDomain, code: LocalErrorCode.requestMalformed.rawValue, userInfo: [NSLocalizedDescriptionKey: description])
self.handleTaskFailure(forRequest: request, task: task, error: error, completionQueue: completionQueue, completion: completion)
return RequestToken(path: request.path, task: nil)
}
return RequestToken(path: request.path, task: requestTask)
}
}`
In the Print statement I get the correct count for the videos on the page so I know that it is working properly inside the function. The request doesn't explicitly say that it returns void but if I try to return a [VIMVideo] object the debug tells me that the expected return is Void and then will not build.
All the information I need I can get, but only inside the function. I would like to be able to use it outside the function but the only way I know how is returning the object which it isn't allowing me to do.
Thanks for your help.
After reading the provided documentation on async, it looks like the callback is the way forward here. However, when looking back at the class method in question it already has a completion handler built in. In the examples given to me by the helpful people here the completion handler would use a simple variable that could be referenced in the callback. The, seemingly, built-in VimeoNetworking Pod completion handler has a complex-looking reference to a response class (which may be part of the responseDictionary?) any ideas on how I can reference that completion handler in a callback? Or is that not the purpose and I should attempt to craft my own completion handler on top of the provided one?
Thanks much!
Hi I'm using a share extension to post some data to my server using API request (Alamofire), the problem is that the request fails immediately and I don't know how to make it work, I read on some articles that I must use URLSession to send the request in the background but I couldn't find any example to how to make it work with alamofire, here is my code in share extension ViewController:
override func didSelectPost() {
MessageHTTPHelper.submitMessage(contains: contentText, completion: { (response) in
self.showAlert(title: "Result", message: response.result.isSuccess ? "SUCCESS" : "FAILURE")
})
}
The MessageHTTPHelper.submitMessage is a helper function that I defined and it works in the main app perfectly
I don't care about the response, I just want to send the request without any callbacks, can you please give me an example of sending a request in iOS share extension?
After lots of search and tests and fails, finally, this solution worked for me!
and here is my code in didSelectPost()
let body: Parameters = [
"version": Configs.currentReleaseVersion,
"content": cleanTextContent
]
let request = HTTPHelper.makeHTTPRequest(route: "message",
headers: HTTPHelper.defaultAuthHTTPHeaders,
verb: .post,
body: body,
apiV1Included: true)
let queue = DispatchQueue(label: "com.example.background", qos: .background, attributes: .concurrent)
request.responseJSON(queue: queue, options: .allowFragments) { (response) in
if response.result.isFailure {
guard let message = response.error?.localizedDescription else {
self.dismiss()
return
}
self.showAlert(title: "Error", message: message)
}
}
The HTTPHepler.makeHTTPRequest is just a helper method which creates an Alamofire DataRequest Instance with given parameters and returns it
This is my first app and I'm wondering if I have made a mistake with regards to using URLSession.shared dataTask because I am not seeing my app get new data which is frequently updated. I see the new json instantly in my browser when I refresh, but, my app apparently does not see it.
Will it ever get the new json data from my server without uninstalling the app?
There are some some similar question topics, such as How to disable caching from NSURLSessionTask however, I do not want to disable all caching. Instead, I want to know how this default object behaves in this scenario - How long is it going to cache it? If indeed the answer is forever, or until they update or reinstall the app, then I will want to know how to reproduce the normal browser based cache behavior, using if-modified-since header, but that is not my question here.
I call my download() function below gratuitously after the launch sequence.
func download(_ ch: #escaping (_ data: Data?, _ respone: URLResponse?, _ error: Error?) -> (), completionHandler: #escaping (_ sessionError: Error?) -> ()) {
let myFileURL: URL? = getFileURL(filename: self.getFilename(self.jsonTestName))
let myTestURL = URL(string:getURLString(jsonTestName))
let session = URLSession.shared
// now we call dataTask and we see a CH and it calls My CH
let task = session.dataTask(with: myTestURL!) { (data, response, error) // generic CH for dataTask
in
// my special CH
ch(data,response,error) // make sure the file gets written in this ch
}
task.resume() // no such thing as status in async here
}
Within the completion handler which I pass to download, I save the data with this code from "ch":
DispatchQueue.main.async {
let documentController = UIDocumentInteractionController.init(url: myFileURL!)
documentController.delegate = self as UIDocumentInteractionControllerDelegate
}
and then finally, I read the data within that same completion handler from disk as such:
let data = try Data(contentsOf: myFileURL!)
For clarification, my complete calling function from which I call download() with completion handler code.
func get_test(){ // download new tests
let t = testOrganizer
let myFileURL: URL? = t.getFileURL(filename:t.getFilename(t.jsonTestName))
t.download( { (data,response,error)
in
var status: Int! = 0
status = (response as? HTTPURLResponse)?.statusCode
if(status == nil) {
status = 0
}
if(error != nil || (status != 200 && status != 304)) {
let alertController = UIAlertController(title: "Error downloading", message:"Could not download updated test data. HTTP Status: \(status!)", preferredStyle: UIAlertControllerStyle.alert)
alertController.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default,handler: nil))
self.present(alertController, animated: true, completion: nil)
self.p.print("END OF COMPLETION HANDLER")
}
else {
let status = (response as! HTTPURLResponse).statusCode
print("Success: status = ", status)
self.p.print("WRITING FILE IN COMPLETION HANDLER")
do {
try data!.write(to: myFileURL!)
DispatchQueue.main.async {
let documentController = UIDocumentInteractionController.init(url: myFileURL!)
documentController.delegate = self as UIDocumentInteractionControllerDelegate
}
} catch {
// // _ = completionHandler(NSError(domain:"Write failed", code:190, userInfo:nil))
print("error writing file \(myFileURL!) : \(error)")
}
self.myJson = self.testOrganizer.readJson()
self.p.print("END OF COMPLETION HANDLER")
}
}, completionHandler: {
sessionError in
if(sessionError == nil) {
print("Downloaded and saved file successfully")
} else {
let alertController = UIAlertController(title: "get_tests", message:
"Failed to download new tests - " + sessionError.debugDescription, preferredStyle: UIAlertControllerStyle.alert)
alertController.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.default,handler: nil))
self.present(alertController, animated: true, completion: nil)
}
})
}
I am needing to implement a progress bar that takes into account a couple of factors.
I have three different classes, my ViewController, a Networking class to handle the network calls and a dataManager class to handle all the db operations.
Now my progressView lives in my viewcontroller and I am looking at a way of updating it as each of the different operations are performed in the other classes.
I am using Alamofire so I know I can use .progress{} to catch the value of the JSON progress but that would also mean exposing the ViewController to the Networking class, which I assume is bad practice?
I think this should be achieved using completion handlers but as I have already setup another thread for handling the JSON / DB operation I'm not wanting to over complicate it anymore than I need to
Networking:
func makeGetRequest(url : String, params : [String : String]?, completionHandler: (responseObject: JSON?, error: NSError?) -> ()) -> Request? {
return Alamofire.request(.GET, url, parameters: params, encoding: .URL)
.progress { _, _, _ in
//bad practice?
progressView.setProgress(request.progress.fractionCompleted, animated: true)
}
.responseJSON { request, response, data, error in completionHandler(
responseObject:
{
let json = JSON(data!)
if let anError = error
{
println(error)
}
else if let data: AnyObject = data
{
let json = JSON(data)
}
return json
}(),
error: error
)
}
}
ViewController:
dataManager.loadData({(finished: Bool, error:NSError?) -> Void in
if let errorMessage = error{
self.syncProgress.setProgress(0, animated: true)
let alertController = UIAlertController(title: "Network Error", message:
errorMessage.localizedDescription, preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default,handler: nil))
self.presentViewController(alertController, animated: true, completion: nil)
}
if finished{
for i in 0..<100 {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {
sleep(1)
dispatch_async(dispatch_get_main_queue(), {
self.counter++
return
})
})
}
}
})
As you can see I am waiting on the finished boolean in the datamanger class to be set before updating the progress bar. The thing is, dataManager makes a call to networking and performs a bunch of other stuff before it finishes, it would be handy to update the progress bar along the way but I'm not sure of the best approach?
DataManager:
func loadData(completion: (finished: Bool, error: NSError?) -> Void) {
var jsonError: NSError?
networking.makeGetRequest(jobsUrl, params: nil) { json, networkError in
//....
}
I'm not too familiar with swift so I can't give you a code example but the way I would do this is create a protocol on your Networking class NetworkingDelegate and implement that protocol in your ViewController. The protocol method would be something like (in objective-c) NetworkingRequestDidUpdatProgress:(float progress)
This is assuming your ViewController calls Networking.makeGetRequest. If it's another class, you would implement the delegate in that class, or you could bubble up the delegate calls to your ViewController through the DataManager class.
I have done some reading and there was recommendation in a similar post (Swift closure with Alamofire) and tried to do the same to my code but I can't find the way to call the function now?
I get an error of: Cannot convert the expression's type '(response: #lvalue String)' to type '((response: String) -> ()) -> ()'
import UIKit
class myClass101: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
var api_error: String = ""
activityInd.startAnimating()
call_api_function(response: api_error)
activityInd.stopAnimating()
if (api_error != "") {
let alertController = UIAlertController(title: "Server Alert", message: "Could not connect to API!", preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default,handler: nil))
self.presentViewController(alertController, animated: true, completion: nil)
}
}
}
the function is as follows:
func call_api_function(completion: (response: String) -> ()) {
let api_url_path: String = "http://\(str_api_server_ip):\(str_api_server_port)"
let api_url = NSURL(string: api_url_path + "/devices.xml")!
Alamofire.request(.GET, api_url)
.authenticate(user: str_api_username, password: str_api_password)
.validate(statusCode: 200..<300)
.response { (request, response, data, error) in
var senderror: String = error!.localizedDescription
completion(response: senderror )
if (error != nil) {
println(senderror)
}
// do other tasks here
}
}
Thanks!
Kostas
Given your definition of call_api_function, you would call it like so:
call_api_function() { response in
activityInd.stopAnimating()
// now use `response` here
}
I'd suggest you do a little research on trailing closures in the The Swift Programming Language: Closures.
But, having said that, your call_api_function has problems of its own.
You're doing a forced unwrapping of the error optional. What if there was no error? Then, the forced unwrapping of the nil optional would fail and the code would crash.
If the request succeeded, you're not doing anything with the data that is returned. Presumably you did the request because you wanted to do something with the returned data.
Unfortunately, you don't provide information about the nature of the XML response you're expecting, but presumably you would instantiate a NSXMLParser instance to parse it and then implement the NSXMLParserDelegate methods and call the parse method.
Following up on the prior points, rather than a closure with a single non-optional parameter, I'd expect to see a closure with two optional parameters, an optional with the parsed data (which would be set if the request and parsing was successful) and an optional with a NSError (which would be set only if there was an error).
A very minor point, but you might want to adopt a Cocoa naming conventions (e.g. camelCase convention of callApiFunction).