I have a method which handles a Apple Push Notification Service remote notification. When this method is executed, I want it to call my server and do a HTTP POST request using the Alamofire library. I want to execute another method that will handle the response of the POST request.
The problem for me is that I am using an existing API to fetch a profile from the server in this POST request. So I need to use this existing API and figure out when this profile fetch is specifically triggered from the remote notification.
Since Alamofire requests are done in a background queue, how would I go about doing an execution of a method after receiving the profile back from the server?
What would be a good option to solving this issue?
Thank you!
Since Alamofire requests are done in a background queue, how would I go about doing an execution of a method after receiving the profile back from the server?
Response handling is built in to Alamofire. You can do something like this (adapted from the docs):
Alamofire.request(.POST, "http://httpbin.org/get", parameters: ["foo": "bar"])
.response { (request, response, data, error) in
println(request)
println(response)
println(error)
}
Note the .response method call, which adds a completion handler to the request object; the completion handler is invoked by Alamofire when the request completes (or fails).
It wasn't clear from your question formulation what problem you were trying to solve. But you've clarified your intent in the question comments above.
As I understand the problem now, you're got some code that updates a profile on the server and handles the server's response. The code is called in two contexts, one initiated by a manual request from the user, another initiated by a push notification. In the first case, you don't want to generate an alert after you process the response from the server, but in the second case you do.
You do indeed have a closure that you can use to handle the different behavior even though the difference happens in the asynchronous part of the process. Here's a sketch (not actual working code) of how that might look:
func updateProfile(parameters: [String:String], showAlert: Bool) {
Alamofire.request(.POST, "http://myserver.com/profile", parameters: parameters)
.response { (request, response, data, error) in
if (error == nil) {
processProfileResponse(response)
if showAlert {
showProfileWasUpdatedAlert()
}
}
}
}
Note the showAlert parameter passed in to the updateProfile method. If you pass in true, it calls the showProfileWasUpdatedAlert method to show your alert after receiving the server's response. Note that this boolean value is "captured" by the closure that handles the Alamofire response because the closure was defined inside the updateProfile function.
This, IMHO, is a better approach than declaring an app global inside your AppDelegate.
Here you go
func AlamofireRequest(method: Alamofire.Method, URLString: URLStringConvertible, parameters: [String : AnyObject]?, encoding: ParameterEncoding, headers: [String : String]?) -> Alamofire.Result<String>? {
var finishFlag = 0
var AlamofireResult: Alamofire.Result<String>? = nil
Alamofire.request(method, URLString, parameters: parameters, encoding: encoding, headers: headers)
.responseString { (_, _, result) -> Void in
if result.isSuccess {
finishFlag = 1
AlamofireResult = result
}
else {
finishFlag = -1
}
}
while finishFlag == 0 {
NSRunLoop.currentRunLoop().runMode(NSDefaultRunLoopMode, beforeDate: NSDate.distantFuture())
}
return AlamofireResult
}
Related
I call a method on the native iOS side from Kotlin/Native framework. The method does its work asynchronously and responds back with some data in a different thread than it was called with.
I want a way to call the response function also in the same thread. Below is the code:
func makePostRestRequest(url: String, body: String) {
let thread1 = Thread.current
let request = NetworkServiceRequest(url: url,
httpMethod: "POST",
bodyJSON: body)
NetworkService.processRequest(requestModel: request) { [weak self] (response: Any?, requestStatus: RequestStatus) in
// This is different thread than "thread1". Need the below code to execute on "thread1"
if requestStatus.result == .fail {
knResponseCallback.onError(errorResponse: requestStatus.error)
} else {
knResponseCallback.onSuccess(response: response)
}
}
}
I have tried to solve this using two ways.
One is to use "semaphores". I just blocked the code execution after the network call and when I got the result back in the callback of network request, I saved it in a variable and signal the semaphore. After that I just call knResponseCallback and use the response variable to return back the response.
Another way I used is to use RunLoops. I got the handle of RunLoop.current, start the runLoop in a mode and in the callback of network request, I just call perform selector on thread with object method of NSObject which dispatches the work to that thread itself.
The problem with both of them is that they are blocking calls. RunLoop.run and semaphore.wait both blocks the thread they are called in.
Is there a way to dispatch some work onto a particular thread from another thread without blocking the particular thread?
You need to create a queue to send the request and use that same queue to handle the response. Something like this should work for you:
let queue = DispatchQueue(label: "my-thread")
queue.async {
let request = NetworkServiceRequest(url: url,
httpMethod: "POST",
bodyJSON: body)
NetworkService.processRequest(requestModel: request) { [weak self] (response: Any?, requestStatus: RequestStatus) in
queue.async {
if requestStatus.result == .fail {
knResponseCallback.onError(errorResponse: requestStatus.error)
} else {
knResponseCallback.onSuccess(response: response)
}
}
}
}
I am using Alamofire and I want to send a get request. If this fails, I want to retry it again after some other operations.
I save the request in a variable:
let myRequest = Alamofire.request("myurl", method: .get)
and in another function:
func retry(request:DataRequest) {
request.responseSwiftyJSON { response in
// ... code to handle the response ...
}
}
In this way, I can call multiple times the retry function, passing the same object myRequest.
However, the request is sent correctly only the first time, then I think it is cached (or in some way the Alamofire object realizes that the response field of the request object is already valid) and the request is not sent again to the server.
To solve this problem, I tried to change a little bit the retry function in this way:
func retry2(request:DataRequest) {
Alamofire.request(request.request!).responseSwiftyJSON { response in
// ... code to handle the response ...
}
}
This should initialize a new Alamofire request using only the URLRequest field of the saved request, but now the request is called twice every time! (I check it in my backend and I am sure this is caused by using this approach).
Is there any way to resend the original saved request? Does Alamofire have some way to initialize a new request from a saved one?
Solution
I ended up by doing something like #JonShier and #DuncanC suggested me:
struct APIRequest {
let url: URLConvertible
let method: HTTPMethod
let parameters: Parameters?
let encoding: ParameterEncoding
let headers: HTTPHeaders?
init(_ url:URLConvertible, method:HTTPMethod = .get, parameters:Parameters? = nil, encoding:ParameterEncoding = URLEncoding.default, headers:HTTPHeaders? = nil) {
self.url = url
self.method = method
self.parameters = parameters
self.encoding = encoding
self.headers = headers
}
func make() -> DataRequest {
return Alamofire.request(url, method: method, parameters: parameters, encoding: encoding, headers: headers)
}
}
I haven't used AlamoFire much, and that minimal use was a couple of years ago.
Looking around in the AlamoFire code, it appears that the request() function returns a DataRequest object. Further, it looks like a DataRequest is a reference type that is meant to track a specific request to a server and it's responses.
It looks to make like once you create one, you use it until it completes or fails, and then discard it. They do not appear to be intended to be reused for subsequent requests of the same type.
To summarize:
Question: "How do I reuse AlamoFire DataRequest objects."
Answer: "Don't do that."
How can I use Alamofire networking framework to add Authorization header to each request, and also do a check whether the response was 401 from the server, so that I can do logic accordingly, and also present some kind of a login view.
Would I need to create some kind of an HttpService class that wraps Alamofire requests? Or is there a more built in way?
As per to your requirement, I would personally prefer to have a intermediate class for calling methods of Alamofire.
For that you can add auth header on each web service call.
Here is the following example for Intermediate class.
WebClient.swift
class WebClient: SessionManager {
static let sharedManager = WebClient()
func responseRequest(_ url: String, method: Alamofire.HTTPMethod, parameter: Parameters? = nil, encoding: ParameterEncoding, header: HTTPHeaders? = nil, completionHandler: #escaping (DefaultDataResponse) -> Void) -> Void {
self.request(url, method: method, parameters: parameter, encoding: encoding, headers: header).response { response in
completionHandler(response)
}
} }
You can modify above class as per requirement or you can directly pass header in request method for each webservice call.
Adding authorization headers for HTTP Basic Auth and checking the status code is fairly easy:
Alamofire
.request(...)
.authenticate(user: "username", password: "password")
.response { response in
if let status = response.response?.statusCode, status == 401 {
// got a 401 response
}
}
I'm struggling with getting this to work to make request to my API. Without a token works, but when I try to add additional headers, things turn to be complicated, for me.
First, the structure.
one class called: APIAsyncTask that makes the requests
one class called APIParams, just a data holder to send parameters to the APIAsyncTask class.
one class called DatabaseAPI that makes that builds the parameters, and send that to the APIAsyncTask class.
DatabaseAPI
func someMethod()
{
let task = APIAsyncTasks()
task.registerCallback { (error, result) -> Void in
print("Finished task, back at DatabaseAPI")
}
let params2 = APIParams(request: .GET, apiPath: "Posts/1", apiToken: "4iTX-56w")
task.APIrequest(params2)
}
APIAsyncTask
This part is for fixing another error, because manager was not global, the task got cancelled quickly.
var manager : Manager!
init(authenticatedRequest : Bool, token: String?)
{
manager = Alamofire.Manager()
print("Pre \(manager.session.configuration.HTTPAdditionalHeaders?.count)")
if(authenticatedRequest && token != nil)
{
var defaultHeaders = Alamofire.Manager.sharedInstance.session.configuration.HTTPAdditionalHeaders!
defaultHeaders["Authorization"] = "bearer \(token)"
let configuration = Manager.sharedInstance.session.configuration
configuration.HTTPAdditionalHeaders = defaultHeaders
manager = Alamofire.Manager(configuration: configuration)
}
print("Post \(manager.session.configuration.HTTPAdditionalHeaders?.count)")
}
After some decision making, it comes down to this part.
private func GetRequest(url: String!,token : String?, completionHandler: (JSON?, NSURLRequest?, NSHTTPURLResponse?, NSError?) -> () ) -> ()
{
print("Begin Get Request")
if(token != nil)//if token is not nil, make authenticated request
{
print("just before request: \(manager.session.configuration.HTTPAdditionalHeaders?.count)")
manager.request(.GET, url, parameters: nil, encoding: .JSON).responseJSON { (request, response, json, error) in
print("Get Request (authenticated), inside alamofire request")
var resultJson : JSON?
if(json != nil)
{
resultJson = JSON(json!)
}
completionHandler(resultJson, request, response, error)
}
}
else
{
//working part without token
So as the code is now, I get an error on completing:
Mattt himself gives the answer of using Alamofire.Manager.sharedInstance.session.configuration.HTTPAdditionalHeaders
, so that should be fine...
I suspect it has something to do with the multiple threads, according to this blog. Or, since it is something about CFNetwork, it could be because my API does not use SSL? I disabled NSAppTransportSecurity
I'm kind of new to swift, so examples would be really appreciated! Thankyou!
So the majority of your code looks solid.
The error leads me to believe that CFNetwork is having difficulty figuring out how to compute the protection space for the challenge. I would also assume you are getting a basic auth challenge since you are attaching an Authorization header.
Digging through your logic a bit more with this in mind led me to see that your not attaching your token to the string properly inside the Authorization header. You need to do the following instead.
defaultHeaders["Authorization"] = "bearer \(token!)"
Otherwise your Authorization header value is going to include Optional(value) instead of just value.
That's the only issue I can see at the moment. If you could give that a try and comment back that would be great. I'll update my answer accordingly if that doesn't actually solve your problem.
Best of luck!
You can add your headers in your request with Alamofire 2 and Swift 2.
For an example: go to example
I'm using Alamofire with Swift kind of this way:
Alamofire.request(.GET, urlString)
.authenticate(usingCredential: credential)
.response {
(request, responseJSON, data, error) in [..and so on]
Now I wonder how I can execute some code in case the server is e.g. completely down. Something like a failed block in ObjC.
I know that I can call something like this to get an error code:
if let response = responseJSON {
var statusCode = response.statusCode
println("-->statusCode: \(statusCode)")
}
But in the case that I can't reach the server, the .response closure won't execute, so there is no error message.
How is this handled?
If you can't reach the server, you will receive a NSURLErrorDomain error via the error variable in the closure.