return array of object with Alamofire - ios

In my app I am using AlamofireObjectMapper.
I want to make a method that returns an array of objects. With the help of Alamofire I made a GET request, which gives the response as responseArray.
With using void function array listOfType always has values.
But when I use non-void function that should return array of object MedicineType, array listOfType is nil.
So here is my code.
func getAll() -> [MedicineType] {
var listOfTypes: [MedicineType]?;
Alamofire.request(BASE_URL, method:.get)
.responseArray(keyPath:"value") {(response: DataResponse<[MedicineType]>) in
if let status = response.response?.statusCode {
switch(status) {
case 200:
guard response.result.isSuccess else {
//error handling
return
}
listOfTypes = response.result.value;
default:
log.error("Error", status);
}
}
}
return listOfTypes!;
}

As i said in my comment, you need to do this in closure, instead of return it, because your call for Alamofire is async so your response will be async
This is an example, you need to add your error handle
func getAll(fishedCallback:(_ medicineTypes:[MedicineType]?)->Void){
var listOfTypes: [MedicineType]?;
Alamofire.request(BASE_URL, method:.get)
.responseArray(keyPath:"value") {(response: DataResponse<[MedicineType]>) in
if let status = response.response?.statusCode {
switch(status) {
case 200:
guard response.result.isSuccess else {
//error handling
return
}
finishedCallback(response.result.value as! [MedicineType])
default:
log.error("Error", status);
finishedCallback(nil)
}
}
}
}
Use it
classObject.getAll { (arrayOfMedicines) in
debugPrint(arrayOfMedicines) //do whatever you need
}
Hope this helps

Try closure
func getAll(_ callback :(medicineTypes:[MedicineType]?) -> Void) -> Void {
Alamofire.request(BASE_URL, method:.get)
.responseArray(keyPath:"value") {(response: DataResponse<[MedicineType]>) in
if let status = response.response?.statusCode {
switch(status) {
case 200:
guard response.result.isSuccess else {
//error handling
return
}
listOfTypes = response.result.value;
callback(listOfTypes)
default:
log.error("Error", status);
callback({})
}
}
}
}

Related

can't return observable of customError in network call

I want to use catchError for getting back my error as custom type.
At first, I want my network layer return Observable and then in ViewModel I subscribed it for .OnNext, .OnError, .OnCompleted events, But I don't know how should I handle Errors such as 4xx, 5xx network status code and then, them return my Custom Error Object!
My Login ViewModel :
func getAccessToken() {
let network = NetworkRequest()
network.logInRequest(tokenType: .guest, token: "cce577f6021608", secondKey: "09128147040", client: "cce577f6021608bc31424d209cbf5120c3683191").subscribe(onNext: { loginData in
self.token.onNext(loginData.access_token)
}, onError: { error in
print("The Error is: \(error.localizedDescription)")
}, onCompleted: {
print("Its Completed")
}).disposed(by: bag)
}
My network layer function:
class NetworkRequest: NSObject {
var rxProvider: MoyaProvider<WebServiceAPIs>
override init() {
rxProvider = MoyaProvider<WebServiceAPIs>( plugins: [ NetworkLoggerPlugin(verbose:true) ])
}
func logInRequest(tokenType: accessTokenTypeEnum, token: String, secondKey: String, client: String) -> Observable<LoginModel> {
return rxProvider.rx
.request(WebServiceAPIs.getAccessToken(tokenType: tokenType.rawValue, token: token, secondKey: secondKey, client: client))
.filterSuccessfulStatusCodes()
.catchError({ error -> Observable<NetworkError> in
return //Observable.just() => I want to return a custom network error as obserable
})
.map(LoginModel.self, atKeyPath: nil, using: JSONDecoder(), failsOnEmptyData: true).asObservable()
}
}
thanks for any help
In my experience, '.materialize()' operator is the perfect solution for handling HTTP errors.
Instead of separate events for success and error you get one single wrapper event with either success or error nested in it.
Moya returns MoyaError enum in error block which you can handle by extracting the error type using switch on MoyaError and then using statusCode to convert to NetworkError enum
func logInRequest(tokenType: accessTokenTypeEnum, token: String, secondKey: String, client: String) -> Observable<LoginModel> {
return sharedProvider.rx
.request(WebServiceAPIs.getAccessToken(tokenType: tokenType.rawValue, token: token, secondKey: secondKey, client: client))
.filterSuccessfulStatusCodes()
.catchError({ [weak self] error -> Observable<NetworkError> in
guard let strongSelf = self else { return Observable.empty() }
if let moyaError = error as? MoyaError {
let networkError = self?.createNetworkError(from: moyaError)
return Observable.error(networkError)
} else {
return Observable.error(NetworkError.somethingWentWrong(error.localizedDescription))
}
})
.map(LoginModel.self, atKeyPath: nil, using: JSONDecoder(), failsOnEmptyData: true).asObservable()
}
func createNetworkError(from moyaError: MoyaError) -> NetowrkError {
switch moyaError {
case .statusCode(let response):
return NetworkError.mapError(statusCode: response.statusCode)
case .underlying(let error, let response):
if let response = response {
return NetworkError.mapError(statusCode: response.statusCode)
} else {
if let nsError = error as? NSError {
return NetworkError.mapError(statusCode: nsError.code)
} else {
return NetworkError.notConnectedToInternet
}
}
default:
return NetworkError.somethingWentWrong("Something went wrong. Please try again.")
}
}
You can create your custom NetworkError enum like below which will map statusCode to custom NetworkError enum value. It will have errorDescription var which will return custom description to show in error view
enum NetworkError: Swift.Error {
case unauthorized
case serviceNotAvailable
case notConnectedToInternet
case somethingWentWrong(String)
static func mapError(statusCode: Int) -> NetworkError {
switch statusCode {
case 401:
return .unauthorized
case 501:
return .serviceNotAvailable
case -1009:
return .notConnectedToInternet
default:
return .somethingWentWrong("Something went wrong. Please try again.")
}
}
var errorDescription: String {
switch self {
case .unauthorized:
return "Unauthorised response from the server"
case .notConnectedToInternet:
return "Not connected to Internet"
case .serviceNotAvailable:
return "Service is not available. Try later"
case .somethingWentWrong(let errorMessage):
return errorMessage
}
}
}

RxMoya Network and Service Error handling in the same function

I am trying to implement a function that handles Network & API errors, my problem is how to emit an observable again after filterSuccessfulStatusCodes().
The main issue I have is that I am not sure if this approach is correct, after the first subscribe.
The current error I have in this code is : Extra argument 'onError' in call
func Request<T: Decodable>(_ request: APIManager) ->
Observable<Result<T>> {
provider.rx
.request(request)
.subscribe(onSuccess: { (response) in
try response.filterSuccessfulStatusCodes()
return Observable.just(response)
.subscribe { event in
switch event {
case .success:
.map(RequestResponse<T>.self)
.map { $0.result! }
.asObservable()
.map(Result.success)
.catchError { error in
return .just(.error(withMessage: "Error \(error)"))
}
case .error:
print("error")
}
}
}, onError: { (error) in
print("network request error")
}, onCompleted: {
print("network request on completed")
}).disposed(by: disposeBag)
}
struct RequestResponse<T: Decodable> {
let statusCode: Int
let statusMessage: String
let success: Bool
let result: T?
let errorBag: String?
}
enum Result<T: Decodable> {
case success(T)
case error(withMessage: String)
}
You can try something like, which converts Single to Observable then call filterSuccessfulStatusAndRedirectCodes and you can handle the errors in catchError closure
func Request<T: Decodable>(_ request: APIManager) -> Observable<Result<T>> {
self.sharedProvider.rx
.request(request)
.asObservable()
.filterSuccessfulStatusAndRedirectCodes()
.map(RequestResponse<T>.self)
.map { Result.success }
.catchError { error in
if let moyaError = error as? MoyaError {
return Objservable.error(handleNetworkError(withMoyaError: moyaError))
} else {
return Observable.error(error)
}
}
}

API Request in ReactiveSwift

I am beginner in ReactiveSwift. I create weather app and my request does not work.
func fetchCurrentWeather() -> SignalProducer<TodayWeatherData?, DownloadError> {
guard let unwrappedURL = url else { return SignalProducer.empty }
return URLSession.shared.reactive
.data(with: URLRequest(url: unwrappedURL))
.retry(upTo: 2)
.flatMapError { error in
print("Error = \(error.localizedDescription)")
return SignalProducer.empty
}
.map { (data, response) -> TodayWeatherData? in
do {
let weatherArray = try JSONDecoder().decode(TodayWeatherData.self, from: data)
return weatherArray
} catch (let error) {
print("\(error)")
return nil
}
}
.observe(on: UIScheduler())
}
self.weatherFetcher.fetchCurrentWeather().map { weather in
}
Map block is not called. What should i change in this request or in parsing method ?
You have to start your SignalProducer.
self.weatherFetcher.fetchCurrentWeather().startWithResult({ result in
switch result {
case .success(let weather): //use the result value
case .failed(let error): //handle the error
}
})
you also have
startWithFailed()
startWithValues()
startWithCompleted()
start()
in all cases, you have to "start" cold signals in order to make them work.

Chain multiple Alamofire requests

I'm looking for a good pattern with which I can chain multiple HTTP requests. I want to use Swift, and preferrably Alamofire.
Say, for example, I want to do the following:
Make a PUT request
Make a GET request
Reload table with data
It seems that the concept of promises may be a good fit for this. PromiseKit could be a good option if I could do something like this:
NSURLConnection.promise(
Alamofire.request(
Router.Put(url: "http://httbin.org/put")
)
).then { (request, response, data, error) in
Alamofire.request(
Router.Get(url: "http://httbin.org/get")
)
}.then { (request, response, data, error) in
// Process data
}.then { () -> () in
// Reload table
}
but that's not possible or at least I'm not aware of it.
How can I achieve this functionality without nesting multiple methods?
I'm new to iOS so maybe there's something more fundamental that I'm missing. What I've done in other frameworks such as Android is to perform these operations in a background process and make the requests synchronous. But Alamofire is inherently asynchronous, so that pattern is not an option.
Wrapping other asynchronous stuff in promises works like this:
func myThingy() -> Promise<AnyObject> {
return Promise{ fulfill, reject in
Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"]).response { (_, _, data, error) in
if error == nil {
fulfill(data)
} else {
reject(error)
}
}
}
}
Edit: Nowadays, use: https://github.com/PromiseKit/Alamofire-
I wrote a class which handles a chain of request one by one.
I created a class RequestChain wich takes Alamofire.Request as parameter
class RequestChain {
typealias CompletionHandler = (success:Bool, errorResult:ErrorResult?) -> Void
struct ErrorResult {
let request:Request?
let error:ErrorType?
}
private var requests:[Request] = []
init(requests:[Request]) {
self.requests = requests
}
func start(completionHandler:CompletionHandler) {
if let request = requests.first {
request.response(completionHandler: { (_, _, _, error) in
if error != nil {
completionHandler(success: false, errorResult: ErrorResult(request: request, error: error))
return
}
self.requests.removeFirst()
self.start(completionHandler)
})
request.resume()
}else {
completionHandler(success: true, errorResult: nil)
return
}
}
}
And I use it like this
let r1 = Alamofire.request(Router.Countries).responseArray(keyPath: "endpoints") { (response: Response<[CountryModel],NSError>) in
print("1")
}
let r2 = Alamofire.request(Router.Countries).responseArray(keyPath: "endpoints") { (response: Response<[CountryModel],NSError>) in
print("2")
}
let r3 = Alamofire.request(Router.Countries).responseArray(keyPath: "endpoints") { (response: Response<[CountryModel],NSError>) in
print("3")
}
let chain = RequestChain(requests: [r1,r2,r3])
chain.start { (success, errorResult) in
if success {
print("all have been success")
}else {
print("failed with error \(errorResult?.error) for request \(errorResult?.request)")
}
}
Importent is that you are telling the Manager to not execute the request immediately
let manager = Manager.sharedInstance
manager.startRequestsImmediately = false
Hope it will help someone else
Swift 3.0 Update
class RequestChain {
typealias CompletionHandler = (_ success:Bool, _ errorResult:ErrorResult?) -> Void
struct ErrorResult {
let request:DataRequest?
let error:Error?
}
fileprivate var requests:[DataRequest] = []
init(requests:[DataRequest]) {
self.requests = requests
}
func start(_ completionHandler:#escaping CompletionHandler) {
if let request = requests.first {
request.response(completionHandler: { (response:DefaultDataResponse) in
if let error = response.error {
completionHandler(false, ErrorResult(request: request, error: error))
return
}
self.requests.removeFirst()
self.start(completionHandler)
})
request.resume()
}else {
completionHandler(true, nil)
return
}
}
}
Usage Example Swift 3
/// set Alamofire default manager to start request immediatly to false
SessionManager.default.startRequestsImmediately = false
let firstRequest = Alamofire.request("https://httpbin.org/get")
let secondRequest = Alamofire.request("https://httpbin.org/get")
let chain = RequestChain(requests: [firstRequest, secondRequest])
chain.start { (done, error) in
}
You have multiple options.
Option 1 - Nesting Calls
func runTieredRequests() {
let putRequest = Alamofire.request(.PUT, "http://httpbin.org/put")
putRequest.response { putRequest, putResponse, putData, putError in
let getRequest = Alamofire.request(.GET, "http://httpbin.org/get")
getRequest.response { getRequest, getResponse, getData, getError in
// Process data
// Reload table
}
}
}
This is definitely the approach I would recommend. Nesting one call into another is very simple and is pretty easy to follow. It also keeps things simple.
Option 2 - Splitting into Multiple Methods
func runPutRequest() {
let putRequest = Alamofire.request(.PUT, "http://httpbin.org/put")
putRequest.response { [weak self] putRequest, putResponse, putData, putError in
if let strongSelf = self {
// Probably store some data
strongSelf.runGetRequest()
}
}
}
func runGetRequest() {
let getRequest = Alamofire.request(.GET, "http://httpbin.org/get")
getRequest.response { [weak self] getRequest, getResponse, getData, getError in
if let strongSelf = self {
// Probably store more data
strongSelf.processResponse()
}
}
}
func processResponse() {
// Process that data
}
func reloadData() {
// Reload that data
}
This option is less dense and splits things up into smaller chunks. Depending on your needs and the complexity of your response parsing, this may be a more readable approach.
Option 3 - PromiseKit and Alamofire
Alamofire can handle this pretty easily without having to pull in PromiseKit. If you really want to go this route, you can use the approach provided by #mxcl.
Here is another way to do this (Swift 3, Alamofire 4.x) using a DispatchGroup
import Alamofire
struct SequentialRequest {
static func fetchData() {
let authRequestGroup = DispatchGroup()
let requestGroup = DispatchGroup()
var results = [String: String]()
//First request - this would be the authentication request
authRequestGroup.enter()
Alamofire.request("http://httpbin.org/get").responseData { response in
print("DEBUG: FIRST Request")
results["FIRST"] = response.result.description
if response.result.isSuccess { //Authentication successful, you may use your own tests to confirm that authentication was successful
authRequestGroup.enter() //request for data behind authentication
Alamofire.request("http://httpbin.org/get").responseData { response in
print("DEBUG: SECOND Request")
results["SECOND"] = response.result.description
authRequestGroup.leave()
}
authRequestGroup.enter() //request for data behind authentication
Alamofire.request("http://httpbin.org/get").responseData { response in
print("DEBUG: THIRD Request")
results["THIRD"] = response.result.description
authRequestGroup.leave()
}
}
authRequestGroup.leave()
}
//This only gets executed once all the requests in the authRequestGroup are done (i.e. FIRST, SECOND AND THIRD requests)
authRequestGroup.notify(queue: DispatchQueue.main, execute: {
// Here you can perform additional request that depends on data fetched from the FIRST, SECOND or THIRD requests
requestGroup.enter()
Alamofire.request("http://httpbin.org/get").responseData { response in
print("DEBUG: FOURTH Request")
results["FOURTH"] = response.result.description
requestGroup.leave()
}
//Note: Any code placed here will be executed before the FORTH request completes! To execute code after the FOURTH request, we need the request requestGroup.notify like below
print("This gets executed before the FOURTH request completes")
//This only gets executed once all the requests in the requestGroup are done (i.e. FORTH request)
requestGroup.notify(queue: DispatchQueue.main, execute: {
//Here, you can update the UI, HUD and turn off the network activity indicator
for (request, result) in results {
print("\(request): \(result)")
}
print("DEBUG: all Done")
})
})
}
}
Details
Alamofire 4.7.2
PromiseKit 6.3.4
Xcode 9.4.1
Swift 4.1
Full Sample
NetworkService
import Foundation
import Alamofire
import PromiseKit
class NetworkService {
static fileprivate let queue = DispatchQueue(label: "requests.queue", qos: .utility)
fileprivate class func make(request: DataRequest) -> Promise <(json: [String: Any]?, error: Error?)> {
return Promise <(json: [String: Any]?, error: Error?)> { seal 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):
DispatchQueue.main.async {
seal.fulfill((nil, error))
}
case .success(let data):
DispatchQueue.main.async {
seal.fulfill(((data as? [String: Any]) ?? [:], nil))
}
}
}
}
}
class 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)
}
}
Main func
func run() {
_ = firstly {
return Promise<Void> { seal in
DispatchQueue.global(qos: .background).asyncAfter(deadline: DispatchTime.now() + .seconds(2)) {
print("1 task finished")
DispatchQueue.main.async {
seal.fulfill(Void())
}
}
}
}.then {
return NetworkService.searchRequest(term: "John").then { json, error -> Promise<Void> in
print("2 task finished")
//print(error ?? "nil")
//print(json ?? "nil")
return Promise { $0.fulfill(Void())}
}
}.then {_ -> Promise<Bool> in
print("Update UI")
return Promise { $0.fulfill(true)}
}.then { previousResult -> Promise<Void> in
print("previous result: \(previousResult)")
return Promise { $0.fulfill(Void())}
}
}
Result
You can use the when method in PromiseKit to attach/append as many calls you want.
Here's an example from PromiseKit docs:
firstly {
when(fulfilled: operation1(), operation2())
}.done { result1, result2 in
//…
}
It worked perfectly for me and it's a much cleaner solution.
Call itself infinitely and DEFINE END CONDITION.
urlring for API link and Dictionary for json
WE may construct the queue model or delegate
func getData(urlring : String , para : Dictionary<String, String>) {
if intCount > 0 {
Alamofire.request( urlring,method: .post, parameters: para , encoding: JSONEncoding.default, headers: nil) .validate()
.downloadProgress {_ in
}
.responseSwiftyJSON {
dataResponse in
switch dataResponse.result {
case .success(let json):
print(json)
let loginStatus : String = json["login_status"].stringValue
print(loginStatus)
if loginStatus == "Y" {
print("go this")
print("login success : int \(self.intCount)")
self.intCount-=1
self.getData(urlring: urlring , para : para)
}
case .failure(let err) :
print(err.localizedDescription)
}
}
}else{
//end condition workout
}
}

Adding a callback to a class function in swift

In my project I created a public class to handle my network interfacing for data requests (JSON, images, etc). The function inside of the class uses Alamofire to establish a network connect and download the JSON file.
The class and the function are below:
import Foundation
import Alamofire
public class DataConnectionManager {
public class func getJSON(AppModule:String, callback:(Int) -> Void) -> Void {
switch(AppModule) {
case "Newsfeed":
Alamofire.request(.GET, "http://some-site.com/api/", encoding: .JSON).responseJSON { (_, _, JSONData, _) in
if JSONData != nil {
jsonHolder.jsonData = JSONData!
print("start")
callback(1)
}
else {
callback(0)
}
}
break
default:
break
}
}
}
I call the function in my project as seen below:
DataConnectionManager.getJSON("Newsfeed", callback: { (intCheck : Int) -> Void in
if intCheck == 1 {
println("Success")
}
else {
println("Failure")
}
})
The app will launch without any errors, however my sanity checks don't print out. In fact, when I do it this way the Alamofire.request doesn't grab the JSON feed either.
Am I heading in the right direction with this?
I got this to work, but I'm not sure how, exactly. I changed a couple of things based on user suggestions (adding error checking, etc) and it magically started working. Here's my updated code so people can see how to add a callback to their functions.
My "ConnectionManager":
import Foundation
import Alamofire
public class DataConnectionManager {
public class func getJSON(AppModule:String, callback:(Int) -> Void) -> Void {
switch(AppModule) {
case "Newsfeed":
Alamofire.request(.GET, "http://some-site.com/api/", encoding: .JSON).responseJSON { (_, _, alamoResponse, error) in
if (error != nil){
println("You've got a response error!")
callback(0)
}
else {
if alamoResponse != nil {
jsonHolder.jsonData = alamoResponse!
callback(1)
}
else {
println("You've got some random error")
callback(0)
}
}
}
break
default:
break
}
}
}
My call to the function:
DataConnectionManager.getJSON("Newsfeed", callback: { (intCheck : Int) -> Void in
if intCheck == 1 {
self.createTable()
}
else {
println("Failure")
}
})
I'm using swift 2.0 + SwiftyJSON and this is my code to implement this:
class func getJSON(AppModule:String, urlToRequest: String, resultJSON:(JSON) -> Void) -> Void {
var returnResult: JSON = JSON.nullJSON
switch(AppModule) {
case "all":
request(.GET, urlToRequest)
.responseJSON { (_, _, result) in
if (result.isFailure){
print("You've got a response error!")
resultJSON(nil)
}
else {
if (JSON(result.value!) != nil) {
returnResult = JSON(result.value!)
resultJSON(returnResult)
}
else {
print("You've got some random error")
}
}
}
break
default:
break
}
}
And call the function like this:
DataManager.getJSON("all",urlToRequest: "myurl.com", resultJSON: { (result: JSON) -> Void in
if (result == nil){
// error with result json == nil
return
}else{
//do something with json result
}
})
Hope this can be helpful.

Resources