I have a function which gets the data from an website api.
func getData() {
print("getData is called")
//create authentication ... omitted
//create authentication url and request
let urlPath = "https://...";
let url = NSURL(string: urlPath)!
let request: NSMutableURLRequest = NSMutableURLRequest(URL: url)
request.setValue("Basic \(base64EncodedCredential)", forHTTPHeaderField: "Authorization")
request.HTTPMethod = "GET"
//some configuration here...
let task = session.dataTaskWithURL(url) { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
let json = JSON(data: data!)
}
task.resume()
}
I am able to get data from API, and I have an observer so that each time when the app goes to foreground the getData() function can be called again to update the data.
override func viewDidLoad() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "willEnterForeground", name: UIApplicationWillEnterForegroundNotification, object: nil)
}
func willEnterForeground() {
print("will enter foreground")
getData()
}
I am pretty sure that when my app goes to foreground, the getData() is called again, however, my data doesn't get updated even when data on the server API has changed. I tried to close the app and open it again, but it still doesn't update the data. So I was wondering if someone can give me some suggestions. Any help will be appreciated!
You need a to make it a closure with a completion. This will enable it to make the request and then call reload "Table View" once you have actually gotten the data back from the request. This is what you need to change on your getData() function
func getData(completion: (json) -> Void)) {
print("getData is called")
//create authentication url and request
let urlPath = "https://...";
let url = NSURL(string: urlPath)!
let request: NSMutableURLRequest = NSMutableURLRequest(URL: url)
request.setValue("Basic \(base64EncodedCredential)", forHTTPHeaderField: "Authorization")
request.HTTPMethod = "GET"
//some configuration here...
let task = session.dataTaskWithURL(url) { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
let json = JSON(data: data!)
}
completion(json)
}
Then you will need to adjust where you call this function because you have changed the parameters it should look more like this.
func willEnterForeground() {
print("will enter foreground")
getData(completion:{
self.data = json
self.tableView.reloadData()
})
}
that will wait till the data is back before it reloads the view updating all your content to reflect what is on the server.
Let me know if you have more questions.
I finally found out it is because I didn't remove the cache. Change the cache policy works then.
Related
There are 2 files:
1st - Network requests
2nd - ViewController, place where the result of getCities() -> Array<String> { ... }
should be called (at leasted could be checked with print
Using this to make a request:
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
if (error != nil) {
print(error as Any)
} else { ...
}
The problem: The result of the request couldn't be accessed by UIViewController until the finish of the request. The list of UIViewController is initiated too early.
P.S: Already tried
semaphore
and
group
but as for me, it works only for the same class/file.
Don't ask, tell
Use a completion handler to notify when the data are available. No semaphore, no group.
func getCities(completion: #escaping ([String]) -> Void) { ... }
and
getCities { [weak self] cities in
self?.list = cities
print(cities)
// do other stuff with received cities
}
Situation I need to fix - Imagine 2 requests are made in same time (when access token is not valid). Both try to refresh token but one of them will invalidate token for another one.
Is there any way how to:
allow only 1 to refresh token
stop all other requests
rerun all stopped requests (when token is refreshed)
Or do you have any idea how to solve this by other way?
This is how my request look like in every view controller:
AF.request(encodedURLRequest, interceptor: AuthInterceptor()).validate().responseData { (response) in
...
}
This is my AuthInterceptor:
final class AuthInterceptor: RequestInterceptor {
func adapt(_ urlRequest: URLRequest, for session: Session, completion: #escaping (Result<URLRequest, Error>) -> Void) {
var adaptedUrlRequest = urlRequest
adaptedUrlRequest.setValue("Bearer \(UserDefaults.standard.getAccessToken())", forHTTPHeaderField: "Authorization")
completion(.success(adaptedUrlRequest))
}
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: #escaping (RetryResult) -> Void) {
print("request \(request) failed")
if let response = request.task?.response as? HTTPURLResponse, response.statusCode == 403 {
guard let url = URL(string: Endpoint.login.url) else { return }
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
urlRequest.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
let parameters: [String: String] = [
"refresh_token": UserDefaults.standard.getRefreshToken(),
"grant_type": "refresh_token"
]
guard let encodedURLRequest = try? URLEncodedFormParameterEncoder.default.encode(parameters,
into: urlRequest) else { return }
AF.request(encodedURLRequest).validate().responseData { (response) in
if let data = response.data {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
if let loginResponse = try? decoder.decode(LoginResponse.self, from: data) {
UserDefaults.standard.setAccessToken(value: loginResponse.accessToken)
UserDefaults.standard.setRefreshToken(value: loginResponse.refreshToken)
completion(.retryWithDelay(1))
}
}
}
}
}
}
You can use Alamofire's RequestRetrier protocol (as part of the RequestInterceptor protocol) to do this without having to manually start and stop requests.
Essentially, you need to know when a refresh is being performed and store any additional completion handlers from requests which failed because of the expired token. You can do this in your AuthInterceptor class. Just make sure your implementation is thread safe!
I am new to swift and doing a project in swift 4.0 to acquire data form Fitbit API and got a Strange problem, my url task does not send any urlrequest any more but skip all the code until task.resume, and do not give anything back. Can anyone helps me plz. The code is shown below
import UIKit
class FitbitAPI{
static let sharedInstance : FitbitAPI = FitbitAPI()
var parsedJson : [Any]? = nil
func authorize(with token: String){
let accessToken = token
let baseURL = URL(string: "https://api.fitbit.com/1/user/-/activities/steps/date/today/1m.json")
let request = NSMutableURLRequest(url:baseURL!)
let bodydata = "access_token=\(String(describing: accessToken))"
request.httpMethod = "GET"
request.setValue("Bearer \(String(describing: accessToken))", forHTTPHeaderField: "Authorization")
request.httpBody = bodydata.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: request as URLRequest, completionHandler: {[weak self] (data, response, error) in
if let error = error {
print(error)
}
if let data = data, error == nil{
do {
self?.parsedJson = (try JSONSerialization.jsonObject(with: data, options: []) as? [Any] )
print(String(describing: self?.parsedJson))
}catch _{
print("Received not-well-formatted JSON")
}
}
if let response = response {
let httpResponse = response as! HTTPURLResponse
print("response code = \(httpResponse.statusCode)")
}
})
task.resume()
}
}
As #Larme implied in his comment, all of that code between the let task = line and the task.resume() line is a callback. Meaning it won't get called until the task completes. Put breakpoints inside of that callback (like on your if let error = error line), and see if they get hit.
ALso, your URL task is a local variable in this method. That means it's entirely possible that its getting released from memory right at the end of this method, before the callback can even be executed. You'll need a reference to the task outside of the method if you want to guarantee that it stays alive in memory long enough to hit the completion callback.
I have the following code which performs a login to a web server, retrieves an API token, and then allows the app to continue. After this code executes, the the calling function uses the login_result to report to the user whether they successfully logged in or need to try again.
Right now, I just have the app sleep for 3 seconds to make sure I have response before moving on. Otherwise, the login results handler starts executing without an answer (because the server hasn't had time to respond).
Obviously, this is a horrible way to do things. What is the correct / swift idiomatic way to handle this?
func remote_login (email: String, password: String) -> Bool {
login_result = false
var jsonResults: NSDictionary = [:]
let account = Account()
let url = NSURL(string: "\(base_api_url())/login?email=\(email)&password=\(password)")
let request = NSMutableURLRequest(URL: url!)
request.HTTPMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.addValue("application/json; charset=UTF-8", forHTTPHeaderField: "Content-Type")
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
do {
jsonResults = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary
account.setCredentials(jsonResults) // we store an API token returned from a successful login; we don't store the users credentials locally
self.login_result = true
}
catch {
print("\n\n\n Login Error \n\n\n")
}
}) // task
task.resume()
sleep(3) //need time for the response to come back...s/b far more elegant way to do this.
return self.login_result
}
Use a closure as your function argument, like so:
func remote_login(email: String, password: String, completion((Bool) -> Void)) {
Then, call the closure when finished (after JSON parsing) like this:
self.login_result = true
completion(login_result)
So, when you use the function, it will look like this:
remote_login("email", "password", {
success in
//Logged in successfully
}
The closure will be called when the HTTP request finishes.
As best I can see:
func remote_login (email: String, password: String, myFunctionCompletionHandler: ()->()) -> Bool {
login_result = false
var jsonResults: NSDictionary = [:]
let account = Account()
let url = NSURL(string: "\(base_api_url())/login?email=\(email)&password=\(password)")
let request = NSMutableURLRequest(URL: url!)
request.HTTPMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.addValue("application/json; charset=UTF-8", forHTTPHeaderField: "Content-Type")
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
do {
jsonResults = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary
// we store an API token returned from a successful login; we don't store the users credentials locally
account.setCredentials(jsonResults)
myFunctionCompletionHandler()
// is this needed as it's handled by the handler?
self.login_result = true
}
catch {
myFunctionCompletionHandler()
print("\n\n\n Login Error \n\n\n")
}
}) // task
task.resume()
return self.login_result
}
You might have to refactor things to handle the completionHandler a little better as I've just literally "plonked it in" but that should do the trick :)..
Just handle and pass the "failed and passed" correctly across to it :)
I'm looking for example code how to prevent redirect response (status code 3xx) when request web api. I'm using Swift with Alamofire 1.2.
I have tried:
delegate.taskWillPerformHTTPRedirection = { (session: NSURLSession!, task: NSURLSessionTask!, response: NSHTTPURLResponse!, request: NSURLRequest!) in
return nil
}
but not work
I've also tried: https://github.com/Alamofire/Alamofire/pull/350/files and have changed my own code to:
var acc = self.txtAccount.text
var pwd = self.txtPassword.text
var url : String = "http://10.1.0.2:8081/wordpress/wp-json/users/me"
let delegate = Alamofire.Manager.sharedInstance.delegate
delegate.taskWillPerformHTTPRedirection = { (session: NSURLSession!, task: NSURLSessionTask!, response: NSHTTPURLResponse!, request: NSURLRequest!) in
var request = NSMutableURLRequest(URL: NSURL(string: url)!)
request.HTTPMethod = "GET"
var credential = "\(acc):\(pwd)"
var authData = credential.dataUsingEncoding(NSUTF8StringEncoding)
var encodedAuthData = authData?.base64EncodedStringWithOptions(nil)
var authValue = "Basic \(encodedAuthData!)"
request.setValue(authValue, forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
return request
}
//I've implemented URLRequestConvertible 'Router'. it also have call the same above url
Alamofire.request(Router.Authorize(acc, pwd))
.response({(request, response, data, error) in
println(request)
})
But it's not worked and seem like turned to infinite loop. I tested on Charles.
Alternative (code snippet) solution using AlamoFire 2.4 (Xcode7). In my case, I always expect a redirect. (I am unpacking a shortened link.) If the completion in the request.response call runs, that is an error to me.
func printRedirectUrl() {
// taskWillPerformHTTPRedirectionWithCompletion: ((NSURLSession, NSURLSessionTask, NSHTTPURLResponse, NSURLRequest, NSURLRequest? -> Void) -> Void)?
Alamofire.Manager.sharedInstance.delegate.taskWillPerformHTTPRedirectionWithCompletion = { session, task, response, request, completion in
// request.URL has the redirected URL inside of it, no need to parse the body
print("REDIRECT Request: \(request)")
if let url = request.URL {
print("Extracted URL: \(url)")
}
Alamofire.Manager.sharedInstance.delegate.taskWillPerformHTTPRedirection = nil // Restore redirect abilities
return
}
// We expect a redirect, so the completion of this call should never execute
let url = NSURL(string: "https://google.com")
let request = Alamofire.request(.GET, url!)
request.response { request, response, data, error in
print("Logic Error, response should NOT have been called for request: \(request)")
Alamofire.Manager.sharedInstance.delegate.taskWillPerformHTTPRedirection = nil // Restore redirect abilities - just in case
}
}
REDIRECT Request: { URL: https://www.google.com/ }
Extracted URL: https://www.google.com/
In Swift 4,
let delegate = Alamofire.SessionManager.default.delegate
delegate.taskWillPerformHTTPRedirection = { (session, task, response, request) -> URLRequest? in
// print("REDIRECT Request: \(request)")
return nil
}
Hello its actually pretty simple
Alamofire has a redirector that will
Example
let request = AF.request("https://google.com",method: .post,parameters: parameters)
.cURLDescription { description in
debugPrint(description)
}
let redirector = Redirector(behavior: .doNotFollow)
request.redirect(using: redirector)
with that it wont redirect
its also in the docs in the advanced usage section
It looks like returning nil can possibly cause a deadlock. Instead, try to create a new NSURLRequest with the same original URL. See #jhersh's notes in a previous Alamofire PR along with the comments and implementation in his tests.
How to Stop a Redirect
func disallowRedirect() {
let URL = "http://google.com/"
let delegate = Alamofire.Manager.sharedInstance.delegate
delegate.taskWillPerformHTTPRedirection = { session, task, response, request in
return NSURLRequest(URL: NSURL(string: URL)!)
}
let request = Alamofire.request(.GET, URL)
request.response { request, response, data, error in
println("Request: \(request)")
println("Response: \(response)")
println("Data: \(NSString(data: data as! NSData, encoding: NSUTF8StringEncoding))")
println("Error: \(error)")
}
}
disallowRedirect()
The fact that you cannot pass nil into the NSURLSessionTaskDelegate method's completionHandler looks like a bug. I'm going to file a radar for this and I'll post a link to the bug report once I'm finished.
I don't know if your version of Alamofire has a support for public delegate. Last time I checked delegate was private. I am using the changes made by #jhersh. You can check his additions and how to use delegate by followin github pr. https://github.com/Alamofire/Alamofire/issues/314