new to swift here.
I'm trying to make an AF.request call inside another AF.request call and everything works fine.
The issue is that the fetchAllUsers() gets called after everything loads up. So instead of getting all the users right away, I have to refresh the page in order to get the fetchAllUsers() to execute.
I thought using closures would avoid this problem but it's still occurring.
Am I doing something wrong?
func fetchAllUsers(completionHandler: #escaping ([User]) -> Void) {
let allUsersUrl = baseUrl + "users/"
let headers: HTTPHeaders = [
.authorization(bearerToken: NetworkManager.authToken)
]
if let url = URL(string: allUsersUrl) {
AF.request(url, headers: headers).responseDecodable(of: [User].self) { response in
if let users = response.value {
completionHandler(users)
} else {
print("Users is empty")
}
}
} else {
print("URL error")
}
}
func login(param: [String:String], completionHandler: #escaping (Int) -> Void) {
let loginUrl = baseUrl + "auth/login"
// Sets the Logged In User
AF.request(loginUrl, method: .post, parameters: param, encoding: JSONEncoding.default, headers: nil).responseString { response in
if let data = response.data {
let authTokenString = String(decoding: data, as:UTF8.self).components(separatedBy: " ")
// Sets the authentication token
NetworkManager.authToken = authTokenString[1]
self.fetchAllUsers { users in
for user in users {
if param["username"] == user.username {
HomeViewModel.loggedInUser = user
}
}
}
completionHandler(response.response!.statusCode)
}
}
}
Pass completion handler of main function and status code to fetchAllUsers and call it there after it's own completion handler
completionHandler(response.response!.statusCode) was being executed before self.fetchAllUsers closure because it was waiting for api response to complete. completionHandler(response.response!.statusCode) was destroying self.fetchAllUsers closure before it is executed, so I called completionHandler(response.response!.statusCode) inside self.fetchAllUsers after it's closure
completionHandler(users)
mainCompletion(statusCode)
Complete code works following
func fetchAllUsers(statusCode:Int, mainCompletion: #escaping (Int) -> Void, completionHandler: #escaping ([User]) -> Void) {
let allUsersUrl = baseUrl + "users/"
let headers: HTTPHeaders = [
.authorization(bearerToken: NetworkManager.authToken)
]
if let url = URL(string: allUsersUrl) {
AF.request(url, headers: headers).responseDecodable(of: [User].self) { response in
if let users = response.value {
completionHandler(users)
mainCompletion(statusCode)
} else {
print("Users is empty")
}
}
} else {
print("URL error")
}
}
func login(param: [String:String], completionHandler: #escaping (Int) -> Void) {
let loginUrl = baseUrl + "auth/login"
// Sets the Logged In User
AF.request(loginUrl, method: .post, parameters: param, encoding: JSONEncoding.default, headers: nil).responseString { response in
if let data = response.data {
let authTokenString = String(decoding: data, as:UTF8.self).components(separatedBy: " ")
// Sets the authentication token
NetworkManager.authToken = authTokenString[1]
self.fetchAllUsers(statusCode: response.response!.statusCode, mainCompletion: completionHandler) { users in
for user in users {
if param["username"] == user.username {
HomeViewModel.loggedInUser = user
}
}
}
}
}
}
Related
I'm trying to create a function which returns Observable<(HTTPURLResponse, Any)> using RxAlamofire:
class APIManager {
let disposeBag = DisposeBag()
func getResponse(credentialData: Credentials, ulr: String)->Observable<(HTTPURLResponse, Any)>{
let credentialData = "\(credentialData.username):\(credentialData.password)".data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue))!
let base64Credentials = credentialData.base64EncodedString()
let headers = ("Authorization", "Basic \(base64Credentials)")
let header = HTTPHeaders.init(dictionaryLiteral: headers)
return Observable.create{ observer in
requestJSON(.get, ulr, parameters: nil, encoding: JSONEncoding.default, headers: header)
.subscribe(onNext: { response in
observer.onNext(response)
} ,
onError: { error in
observer.onError(error)
},
onCompleted: {},
onDisposed: {}).disposed(by: self.disposeBag)
return Disposables.create()
}
}
}
The code into onNext is not executed; it's going through onDisposed only.
How can I fix it?
Why are you wrapping requestJSON function with another Observable which is just forwarding the same element and error, when that function is already returning the Observable you need?.
Isn't this enough for you?
class APIManager {
func getResponse(credentialData: Credentials, ulr: String) -> Observable<(HTTPURLResponse, Any)> {
let credentialData = "\(credentialData.username):\(credentialData.password)"
.data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue))!
let base64Credentials = credentialData.base64EncodedString()
let headers = ("Authorization", "Basic \(base64Credentials)")
let header = HTTPHeaders.init(dictionaryLiteral: headers)
return requestJSON(.get, ulr, parameters: nil, encoding: JSONEncoding.default, headers: header)
}
}
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
when I call this function the array 'data' is showing empty square brackets, it is not giving me any errors though
here is the code :-
import Foundation
import Alamofire
import SwiftyJSON
class Networking {
var data = [Item]()
let tVC = TableViewController()
let url = "https://api.coinmarketcap.com/v1/ticker/"
func getCoinData(url: String) {
Alamofire.request(url, method: .get)
.responseJSON { response in
if response.result.isSuccess {
let coinJSON : JSON = JSON(response.result.value!)
for i in 0..<coinJSON.count{
let coinName = Item(bitJSON: coinJSON[i])
self.data.append(coinName)
self.tVC.tableView.reloadData()
}
}
else {
print("Error: \(String(describing: response.result.error))")
}
}
}
}
Try this one.
Change your url, method is "Get" and your mapping model.
public static func getArrayInformation(completionHandler: #escaping (APIResponse) -> Void) {
let url = "your url"
let params: Parameters = ["username": "admin",
"password": "1234"]
Alamofire.request(url,
method: .post,
parameters: params,
encoding: JSONEncoding.default,
headers: ["Content-Type": "application/json"])
.responseDecodableObject(keyPath: nil, decoder: JSONDecoder(), completionHandler: { (handler: DataResponse<[Your Object Array]>) in
completionHandler(handler.result.value ?? [Your Object Array]())
})
}
why you reload Table every loop iteration , and instead Loop use Map
class Networking {
var data = [Item]()
let tVC = TableViewController()
let url = "https://api.coinmarketcap.com/v1/ticker/"
func getCoinData(url: String) {
Alamofire.request(url, method: .get)
.responseJSON { response in
if response.result.isSuccess {
let coinJSON : JSON = JSON(response.result.value!)
data = coinJSON.map({ (coinJson) -> Item in
return Item(bitJSON: coinJson)
})
DispatchQueue.main.async {
self.tVC.tableView.reloadData()
}
}
else {
print("Error: \(String(describing: response.result.error))")
}
}
}
}
I have the function, that should send video position time, when user closes avplayercontroller, here is it:
let concurrentQueue = DispatchQueue(label: "networkRequest", attributes: .concurrent)
fileprivate func sendBackgroundRequest(urlString: String, method: HTTPMethod, parameters: Parameters?) {
let headers = createHeader()
print(headers)
if networkManager!.isReachable {
concurrentQueue.async {
let request = Alamofire.request(urlString, method: method, parameters: parameters, encoding: JSONEncoding.default, headers: headers)
request.responseJSON { data in
if data.result.error == nil {
if data.response?.statusCode == 200 {
print("Success")
} else {
print(data.response?.statusCode)
}
} else {
print(data.result.error)
}
}
}
}
}
func sendSeriesDurationTime(provider: String, fileID: String, timePosition: Int) {
let parameters: Parameters = [
"time_position": timePosition
]
let dataUrl = "\(BASE_URL)\(PLAYED_DURATION_SLUG)\(provider)/\(fileID)"
print(dataUrl)
sendBackgroundRequest(urlString: dataUrl, method: .post, parameters: parameters)
}
I call it in viewWillDisappear method. But when I click "done" button of player, it takes some time, before it dissappears, because system waits before alamofire gets response to completion.
I know, that I can call it in viewDidDisappear method, but this way my UI freezes in another controller....
I found the answer, just needed to delete
request.responseJSON { data in
if data.result.error == nil {
if data.response?.statusCode == 200 {
print("Success")
} else {
print(data.response?.statusCode)
}
} else {
print(data.result.error)
}
}
so request made, but UI is not freezing, and system is not waiting server response.
I'm using RxSwift to simply my code. For my current project I'd like to apply RxSwift's principles to a mess of completion blocks from the LayerKit:
layerClient.connectWithCompletion { (success, error) -> () in
if (!success) {
// Error
} else {
layerClient.requestAuthenticationNonceWithCompletion { (nonce, error) -> () in
// Even more blocks
}
}
}
I'm thinking about something like this:
// In extension
public func rx_connect() -> Observable<Bool> {
return create { observer in
self.connectWithCompletion { (success, error) -> ()in
if (success) {
observer.on(.Next(success))
observer.on(.Completed)
} else {
observer.on(.Error(error))
}
}
return NopDisposable.instance
}
}
public func rx_requestAuthenticationNonce() -> Observable<String> {
// Same for annother method
}
// In AppDelegate
self.layerClient.rx_connect()
.then() // requestAuthenticationNonceWithCompletion and use the nonce for next action
.then()
.subscribeNext(…
.onError(… // To catch all errors
RxSwift does not have a then() method. Is there another way to do this chaining stuff or am I thinking wrong on how to use ReactiveX in general?
Right, RxSwift doesn't have the then operator because the name is very risky.
then can be a lot of things in the Reactive Extensions world a map a flatMap or even a switchLatest, depending on the context.
In this case I would suggest to use a flatMap because that is going to be returned using a map is an Observable of Observables, that in most cases are hard to combine and manage. So I would do:
self.layerClient.rx_connect()
.flatMap(){ _ in
return rx_requestAuthenticationNonce()
.catchError(displayError)
}
.subscribeNext(…
.onError(… // To catch all errors
I would use flatMap instead of map in this case because I am then able to catch errors without terminating the sequence in case rx_requestAuthenticationNonce() fails.
Be careful in using the catchError operator, this operator will terminate the sequence after recovering and any extra event will be ignored after that.
This is authorization part in my project. Just an example for chaining requests. It turns the result of chained requests into a bool answer. It use 'flatMap' because result of old request is used in building new request. Otherwise, you can use 'concat' instead of 'flatMap'.
let client = RxSimpleLazyHttpClient.sharedInstance
let uuid = UIDevice().identifierForVendor!.UUIDString
let helloheaders = ["X-User-Identifier": "id:" + uuid]
let helloRequest: Observable<StringDictionary> = client.makeRequest(verb: .GET, url: NSURL(string: self.API_HOST + self.API_HELLO)!, parameters: [:], headers: helloheaders)
.map { result in
if (self.enableDebugOutput) {
self.debug(result)
}
let json = JSON(data: result.2!)
let userKey = json["user_key"].string
let userSecretHash = json["user_secret_hash"].string
return ["user_key": userKey ?? "", "user_secret_hash": userSecretHash ?? ""]
}
let jointAuthRequest: (StringDictionary -> Observable<StringDictionary>) = { [unowned self] stringDictionary in
Logger.D(stringDictionary.debugDescription)
let userKey = stringDictionary["user_key"]
let headers = ["X-User": userKey ?? ""]
return client.makeRequest(verb: .GET, url: NSURL(string: self.API_HOST + self.API_AUTH)!, parameters: [:], headers: headers)
.map { result in
if (self.enableDebugOutput) {
self.debug(result)
}
let json = JSON(data: result.2!)
let nonce = json["nonce"].string
var result = [String: String]()
result["nonce"] = nonce
return result.merge(stringDictionary)
}
}
let jointAuthNonceRequest: (StringDictionary -> Observable<StringDictionary>) = { [unowned self] stringDictionary in
Logger.D(stringDictionary.debugDescription)
let nonce = stringDictionary["nonce"] ?? ""
let userSecretHash = stringDictionary["user_secret_hash"] ?? ""
let headers = ["X-Pass": (userSecretHash + nonce).sha1().webSafeBase64()]
return client.makeRequest(verb: .GET, url: NSURL(string: self.API_HOST + self.API_AUTH + "/" + nonce)!, parameters: [:], headers: headers)
.map { result in
if (self.enableDebugOutput) {
self.debug(result)
}
let json = JSON(data: result.2!)
let sessionKey = json["session_key"].string
let sessionSecret = json["session_secret"].string
var result = [String: String]()
result["session_key"] = sessionKey
result["session_secret"] = sessionSecret
return result.merge(stringDictionary)
}
}
let jointResult: (StringDictionary -> Observable<Bool>) = { result in
let userKey = result["user_key"]
let userSecretHash = result["user_secret_hash"]
let sessionKey = result["session_key"]
let sessionSecret = result["session_secret"]
if userKey == nil || userSecretHash == nil || sessionKey == nil || sessionSecret == nil {
Logger.D("Auth Fail")
return just(false)
}
/* You can store session key here */
return just(true)
}
return helloRequest
.shareReplay(1)
.observeOn(RxScheduler.sharedInstance.mainScheduler)
.flatMap(jointAuthRequest)
.flatMap(jointAuthNonceRequest)
.flatMap(jointResult)
This is the 'makeRequest' method.
public func makeRequest(verb verb: Alamofire.Method, url: NSURL, parameters: [String : String]?, headers: [String : String]?) -> Observable<RxRequestResult> {
return create { observer in
Alamofire.request(verb, url, parameters: nil, encoding: ParameterEncoding.URL, headers: headers)
.response { request, response, data, error in
observer.onNext((request, response, data, error))
}
return AnonymousDisposable {
// when disposed
}
}
}