API Request in ReactiveSwift - ios

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.

Related

How can i make my Networking class generic in Swift?

Here i am extracting data as DataModel. But i want to make this class generic and pass the model myself so that i can use it to parse data from multiple API's. Can Anyone Help?
import Foundation
struct NetworkManager {
func fetchData(url : String, completion : #escaping (DataModel?) -> ()) {
print("Neeraj here")
let sessionURL = URL(string: url)
let session = URLSession(configuration: .default)
let dataTask = session.dataTask(with: sessionURL!) { (data, response, error) in
if error == nil {
if let safeData = data {
if let parsedData = self.parseData(data : safeData) {
print("got data")
completion(parsedData)
}
else {
debugPrint("failed to fetch data")
completion(nil)
}
}
}
else {
print("error in data task is \(String(describing: error))")
completion(nil)
}
}
dataTask.resume()
}
func parseData(data : Data) -> DataModel? {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(DataModel.self, from: data)
return decodedData
} catch {
print("error while parsing data \(error)")
return nil
}
}
}
With the convenient Result type you can write a quite tiny generic method, it returns the decoded type on success and any error on failure
func fetchData<T: Decodable>(urlString: String, completion: #escaping (Result<T,Error>) -> Void) {
guard let url = URL(string: urlString) else { return } // or throw an error
URLSession.shared.dataTask(with: url) { (data, _, error) in
if let error = error { completion(.failure(error)); return }
completion( Result{ try JSONDecoder().decode(T.self, from: data!) })
}.resume()
}
Note: Force unwrapping data! is 100% safe if no error occurs
Be aware that you have to specify the concrete type when you are going to call the method
fetchData(urlString: "https://example.com/api") { (result : Result<MyModel,Error>) in
switch result {
case .success(let model): print(model)
case .failure(let error): print(error)
}
}
You can add a generic type constraint (called Model) which conforms Decodable like below:
struct NetworkManager {
func fetchData<Model: Decodable>(url : String, completion : #escaping (Model?) -> ()) {
let sessionURL = URL(string: url)
let session = URLSession(configuration: .default)
let dataTask = session.dataTask(with: sessionURL!) { (data, response, error) in
if error == nil {
if let safeData = data {
do {
let decodedData = try JSONDecoder().decode(Model.self, from: safeData)
completion(decodedData)
} catch {
print("error while parsing data \(error)")
}
} else {
debugPrint("failed to fetch data")
completion(nil)
}
}
else {
print("error in data task is \(String(describing: error))")
completion(nil)
}
}
dataTask.resume()
}
}
Usage
struct SampleModel: Decodable {
let name: String
}
NetworkManager().fetchData(url: "") { (data: SampleModel?) in
print(data)
}
You can write a generic function to fetch data like this one :
func fetchGenericData<T: Decodable>(urlString: String, completion: #escaping (T) -> ()) {
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!) { (data, resp, err) in
if let err = err {
print("Failed to fetch data:", err)
return
}
guard let data = data else { return }
do {
let obj = try JSONDecoder().decode(T.self, from: data)
completion(obj)
} catch let jsonErr {
print("Failed to decode json:", jsonErr)
}
}.resume()
}
}
I suppose that you have a data model, if you have not, you should create for your every object. Also by using a dummy URL i will make a request and fetch the JSON includes some users name and ids with JSON format.
Let`s define a data model for this:
struct StackUser: Decodable {
let id: Int
let name: String
}
fetchGenericData(urlString: "https://api.stackoverexample.com/stackusers") { (stackUsers: [StackUser]) in
stackUsers.forEach({print($0.name)})
}
Finally you will be parse the data and prints like this:
Rob
Matt
Vadian

Networking Layer in Swift , Completion Blocks and Errors

I am implementing a Networking Layer in Swift. Here is one of the functions. The function works as expected but I want to improve upon it. I am using DispatchQueue to make sure that the callback from the network client is always on the main thread. This ends up repeating the DispatchQueue.main.async in 3 different places.
Also, when I encounter some error when performing the request I still send back nil but as a success.
func getAllStocks(url: String, completion: #escaping (Result<[Stock]?,NetworkError>) -> Void) {
guard let url = URL(string: url) else {
completion(.failure(.invalidURL)) // wrap in DispatchQueue also
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
DispatchQueue.main.async {
completion(.success(nil)) // should I send nil or some sort of failure
}
return
}
let stocks = try? JSONDecoder().decode([Stock].self, from: data)
DispatchQueue.main.async {
completion(.success(stocks))
}
}
}
How can I minimize the code or is it fine?
The goal of the Result type is that you return a non-optional type on success and an error on failure.
I recommend to call completion on the current thread and dispatch the result on the caller side.
And handle also the DecodingError
func getAllStocks(url: String, completion: #escaping (Result<[Stock],Error>) -> Void) {
guard let url = URL(string: url) else {
completion(.failure(NetworkError.invalidURL))
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error { completion(.failure(error)); return }
// if error is nil then data has a value
do {
let stocks = try JSONDecoder().decode([Stock].self, from: data!)
completion(.success(stocks))
} catch {
completion(.failure(error))
}
}.resume()
}
getAllStocks(url: someURL) { result in
DispatchQueue.main.async {
switch result {
case .success(let stocks): print(stocks)
case .failure(let networkError as NetworkError): handleNetworkError(networkError)
case .failure(let decodingError as DecodingError): handleDecodingError(decodingError)
case .failure(let error): print(error)
}
}
}
Lean into the build-in constructs and standard types.
func getAllStocks(url: String, completion: #escaping (Result<[Stock], Error>) -> Void) {
func completeOnMain(_ result: Result<[Stock], Error>) { // <-- Nested function
DispatchQueue.main.async { completion(result) } // <-- Handle repeated work
}
guard let url = URL(string: url) else {
completeOnMain(.failure(URLError(.badURL))) // <-- Standard Error
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
do {
if let error = error { throw error }
guard let data = data else { throw URLError(.badServerResponse) }
let stocks = try JSONDecoder().decode([Stock].self, from: data)
completeOnMain(.success(stocks))
} catch {
completeOnMain(.failure(error)) // <-- Unified error handling
}
}
}
A nested function is used to do the repeated work of dispatching to the main thread.
Standard error are used instead of defining custom errors.
A do/catch and throws are used to handle all the errors at once.
I have one final note: Async functions should always be async. The bad URL error should not call completion(_:) directly; use DispatchQueue.main.async to make sure the call happens in a later run loop.

Avoid Swift Calling methods in viewDidLoad at the same time and getting "Index out of range"

could you give me a hand here? I would like to know how to call a swift function once another one is over, I think it is calling both functions at the same time. This is my viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
downloadJsonEscuelas(id_responsable: "5")
downloadJsonAnuncios(id_escuela:("\arrayEscuelas[0].id_escuela!)"))
}
The first function "downloadJsonEscuelas" fills the array "arrayEscuelas" with data, and the second function receives the data as a parameter:
downloadJsonEscuelas:
func downloadJsonEscuelas(id_responsable: String) {
guard let downloadURL = URL(string: Constantes.URLbase+"json/getescuelas.php?id="+id_responsable) else { return }
URLSession.shared.dataTask(with: downloadURL) { data, urlResponse,
error in
guard let data = data, error == nil, urlResponse != nil else {
print("Something went wrong")
return
}
print("Escuelas downloaded")
do
{
let downloadedEscuelas = try JSONDecoder().decode([Escuelas].self, from: data)
self.arrayEscuelas = downloadedEscuelas
DispatchQueue.main.async {
print("id escuela actual:"+self.idEscuelaActual!+":)")
self.escuelaTextBox.text = self.arrayEscuelas[0].escuela!
self.idEscuelaActual = "\(self.arrayEscuelas[0].id_escuela!)" as String
}
} catch let jsonErr {
print("Error: ", jsonErr)
}
}.resume()
}
downloadJsonAnuncios:
func downloadJsonAnuncios(id_escuela: String) {
guard let downloadURL = URL(string: Constantes.URLbase+"json/getanuncios.php?id="+id_escuela) else { return }
URLSession.shared.dataTask(with: downloadURL) { data, urlResponse,
error in
guard let data = data, error == nil, urlResponse != nil else {
print("Something went wrong")
return
}
print("downloaded")
do
{
let downloadedAnuncios = try JSONDecoder().decode([Anuncios].self, from: data)
self.arrayAnuncios = downloadedAnuncios
print(self.arrayAnuncios[0].titulo!)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch let jsonErr {
print("Error: ", jsonErr)
}
}.resume()
}
I think it calls both functions at the same time, so it's not filling the array in the first function. When I pass the parameter as a plain number everything goes well, something like:
downloadJsonAnuncios(id_escuela: "2")
I hope you could help me here, thank you so much.
Insert second call ( this downloadJsonAnuncios(id_escuela:("\arrayEscuelas[0].id_escuela!)")) ) inside the first call ( downloadJsonEscuelas(id_responsable: "5")) 's completion
func downloadJsonEscuelas(id_responsable: String) {
guard let downloadURL = URL(string: Constantes.URLbase+"json/getescuelas.php?id="+id_responsable) else { return }
URLSession.shared.dataTask(with: downloadURL) { data, urlResponse,
error in
guard let data = data, error == nil, urlResponse != nil else {
print("Something went wrong")
return
}
print("Escuelas downloaded")
do
{
let downloadedEscuelas = try JSONDecoder().decode([Escuelas].self, from: data)
self.arrayEscuelas = downloadedEscuelas
DispatchQueue.main.async {
print("id escuela actual:"+self.idEscuelaActual!+":)")
self.escuelaTextBox.text = self.arrayEscuelas[0].escuela!
self.idEscuelaActual = "\(self.arrayEscuelas[0].id_escuela!)" as String
}
// here
downloadJsonAnuncios(id_escuela:("\arrayEscuelas[0].id_escuela!)"))
} catch let jsonErr {
print("Error: ", jsonErr)
}
}.resume()
}
Why not call the second one in the closure of the first one
do {
let downloadedEscuelas = try JSONDecoder().decode([Escuelas].self, from: data)
self.arrayEscuelas = downloadedEscuelas
DispatchQueue.main.async {
print("id escuela actual:"+self.idEscuelaActual!+":)")
self.escuelaTextBox.text = self.arrayEscuelas[0].escuela!
self.idEscuelaActual = "\(self.arrayEscuelas[0].id_escuela!)" as String
}
downloadJsonAnuncios(id_escuela:("\arrayEscuelas[0].id_escuela!)"))
} catch let jsonErr {
print("Error: ", jsonErr)
}
}.resume()
By default the code in viewDidLoad runs synchronously on the UIThread.
Your API call is running on a separate thread and is allowing your other method to be executed before it's completed.
You should probably be using a completion handler in your method so your second method only gets called after your api call completes.
See this answer for implementing CompletionHandler

return array of object with Alamofire

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({})
}
}
}
}

How to map RxSwift Observable and Result

I have a quick question:
I have a network request that returns Observable<Result<String, RequestError>>, let’s call it requestToken
if this request succeeds, I want to use the String (token) to do another request that returns Observable<Result<NSDictionary, RequestError>>, let’s call it requestData
when that second request comes back, I wanna merge the token into its dictionary
in the end I wanna map from Observable<Result<String, RequestError>> to Observable<Result<NSDictionary, RequestError>>
How can I achieve that without multiple nested levels in my code?
This is what I have today:
requestToken()
.flatMap({ result -> Observable<Result<NSDictionary, RequestError>> in
switch result {
case .success(let token):
return requestData(token: token).map({ $0.map({ $0 + ["token": token] }) })
case .failure(let error):
return Observable.of(.failure(error))
}
})
Updated:
It's a detailed example, hope this may help:
enum RequestError: Error {
case unknown
}
func requestToken() -> Observable<String> {
return Observable.create { observer in
let success = true
if success {
observer.onNext("MyTokenValue")
observer.onCompleted()
} else {
observer.onError(RequestError.unknown)
}
return Disposables.create()
}
}
func requestData(token: String) -> Observable<[String: Any]> {
return Observable<[String: Any]>.create { observer in
let success = false
if success {
observer.onNext(["uid": 007])
observer.onCompleted()
} else {
observer.onError(RequestError.unknown)
}
return Disposables.create()
}
.map { (data: [String: Any]) in
var newData = data
newData["token"] = token
return newData
}
}
requestToken() // () -> Observable<String>
.flatMapLatest(requestData) // Observable<String> -> Observable<[String: Any]>
.materialize() // Observable<[String: Any]> -> Observable<Event<[String: Any]>>
.subscribe(onNext: { event in
switch event {
case .next(let dictionary):
print("onNext:", dictionary)
case .error(let error as RequestError):
print("onRequestError:", error)
case .error(let error):
print("onOtherError:", error)
case .completed:
print("onCompleted")
}
})
.disposed(by: disposeBag)
Original:
I think it's much easier to achieve it using materialize() with less extra work:
func requestToken() -> Observable<String> { return .empty() }
func requestData(token: String) -> Observable<NSDictionary> { return .empty() }
enum RequestError: Error {}
requestToken()
.flatMapLatest(requestData)
.materialize()
.subscribe(onNext: { event in
switch event {
case .next(let dictionary):
print("onNext:", dictionary)
case .error(let error as RequestError):
print("onRequestError:", error)
case .error(let error):
print("onOtherError:", error)
case .completed:
print("onCompleted")
}
})
.disposed(by: disposeBag)
Hope this may help.
If you use the built in error system, you can save yourself from having to manually pass the error along and all the switches that would entail. You can cast the error at the end.
I would do something more like this:
// this is necessary to handle adding the token to the dictionary.
extension Dictionary {
/// An immutable version of update. Returns a new dictionary containing self's values and the key/value passed in.
func updatedValue(_ value: Value, forKey key: Key) -> Dictionary<Key, Value> {
var result = self
result[key] = value
return result
}
}
// function signatures, note that they don't return Results anymore.
func requestToken() -> Observable<String> { /*...*/ }
func requestData(withToken: String) -> Observable<[String: Any]> { /*...*/ }
requestToken().flatMapLatest {
requestData(token: $0)
.map { $0.updatedValue($0, forKey: "token") }
.map { .success($0) }
}.catchError {
Observable.just(.failure($0 as! RequestError))
}
With the above, the end result would be an Observable<Result<[String: Any], RequestError>> just like in your case, but the error handling is much cleaner.
If you can't change the signatures of the two functions you are using then I would do this:
func throwError<T, U: Error>(result: Result<T, U>) throws -> T {
switch result {
case .success(let token):
return token
case .failure(let error):
throw error
}
}
requestToken().map {
try throwError(result: $0)
}.flatMapLatest {
requestData(token: $0)
.map { try throwError(result: $0) }
.map { $0.updatedValue($0, forKey: "token") }
}
.map { .success($0) }
.catchError {
Observable.just(.failure($0 as! RequestError))
}

Resources