RxSwift & alamofire, How to send http request after previous request arrived? - alamofire

I need to send http request in RxSwift & Alamofire circumstance and send it synchronously which means ...
send(1)...response(1)
-------------------- send(2)...response(2)
-----------------------------------------send(3)...response (3)
and This is my code
Observable.from(devicesArray)
.concatMap { (device) in
return HTTPRequest.deleteDevice(withDevice: device)
}.subscribe({ (event) in
log.debug("Remove device successfully")
}).disposed(by: self.disposeBag)
and deleteDevice is
func deleteDevice(withDevice device:Device) -> Single<String> {
return Alamofire.request("http://example.com/\(device.deviceId)", method: .delete, parameters: nil, headers: self.headers()).rx
.responseJSON()
.retry(self.retryMaxAttempCount)
.asSingle()
.observeOn(SerialDispatchQueueScheduler(qos: .default))
.map({ (response) in
guard let json = response.value as? [String: Any] else {
throw HTTPManagerError.invalidResponse
}
guard let resultCode = json["resultCode"] as? String else {
throw HTTPManagerError.invalidResponse
}
if resultCode == "0000" || resultCode == "0101" {
return resultCode
} else {
throw HTTPManagerError.invalidResponse
}
})
.observeOn(MainScheduler.instance)
}
Now every single HTTPRequest.deleteDevice function requested in parallel.
So How could I make this request wait until a previous request get finished?
Thanks.

The key here is to make an array of observables and concat them:
// first we create a deleteDevice observable for each item.
// Remember, they don't actually make a network call until subscribed to and concat only subscribes to each once the previous one is done.
Observable.concat(
devicesArray.map { deleteDevice(withDevice: $0) }
.map { $0.asObservable() } // because concat doesn't exist for Singles.
.map { $0.catchErrorJustReturn("\($0) not deleted.") } // make sure that all deletes will be attempted even if one errors out.
)
// the above will emit a string every time a delete finishes.
// `.toArray()` will gather them together into an array.
.toArray()
.subscribe(onNext: { results in
})
.disposed(by: disposeBag)

Related

RxSwift combine observable with conditional

I'm trying to combine observables and I want them to run in sequence (e.g., perform step 1, if some condition is met then perform step 2, if some condition is met then perform step 3). The only way I've found to do this is to add the conditions to each step, which I'm not a fan of: Here's a sample of my current solution:
enum Status {
case unknown, exists, missing
}
func refresh() -> Observable<Status> {
return checkLocalStatus()
.flatMapLatest { $0 == .exists ? Observable.just($0) : self.attemptRemoteStatusOverride() }
.flatMapLatest { $0 == .exists ? Observable.just($0) : self.attemptRemoteStatusUpdate() }
}
private func checkLocalStatus() -> Observable<Status> {
return Observable.create { observer in
// Regarding Maxim Volgin's comment, here I'm converting a closure to an
// observable... why not use Observable.create?
self.cache.status { (status) in
guard status != .exists else {
observer.onNext(status) // .exists
observer.onCompleted()
}
/* I don't want this condition to be here */
if ignoreRemote {
// status is !exists and we should ignore remote, throw error
observer.onError(Errors.remoteDisabled)
}
observer.onNext(.missing)
observer.onCompleted()
}
}
}
private func attemptRemoteStatusOverride() -> Observable<Status> {
return remote.statusOverride()
}
private func attemptRemoteStatusUpdate() -> Observable<Status> {
return Observable.create { observer in
// Regarding Maxim Volgin's comment, here I'm converting a closure to an
// observable... why not use Observable.create?
self.remote.updateStatus { (status, error) in
guard error == nil else {
observer.onError(error!)
}
observer.onNext(status)
observer.onCompleted()
}
}
}
I'd like to do something like:
func refresh() -> Observable<Status> {
return checkLocalStatus()
.if({ $0 != .exists && !ignoreRemote },
then: { self.attemptRemoteStatusOverride() },
else: { return $0 })
.if({ $0 != .exists },
then: { self.attemptRemoteStatusUpdate() },
else: { return $0 })
}
or
func refresh() -> Observable<Status> {
return checkLocalStatus()
.flatMapLatest(if: { $0 != .exists && !ignoreRemote }) { self.attemptRemoteStatusOverride() }
.flatMapLatest(if: { $0 != .exists }) { self.attemptRemoteStatusUpdate() }
}
I haven't been able to find anything like what I'm attempting, so I assume I'm going about this wrong. Does anyone have suggestions or alternatives on how to go about this route of combining observables? I've seen examples using combineLatest and returning some results based on the result of something else, but I want to perform each step only if a condition is met. combineLatest would perform each step (every time) and then I would return the result(s) of some steps based on the output of other steps. I also started looking into writing a custom operator, but can't figure a way to do it.
Update: I've changed to the following and plan to write a method to remove duplication:
func refresh() -> Observable<Status> {
return checkLocalStatus()
.flatMapLatest { status -> Observable<Status>
guard status != .exists && !ignoreRemote else {
return Observable.just(status)
}
return self.attemptRemoteStatusOverride()
}
.flatMapLatest { status -> Observable<Status>
guard status != .exists && !ignoreRemote else {
return Observable.just(status)
}
return self.attemptRemoteStatusUpdate()
}
}
Maybe you need some version of flatMapLatest function with conditions? You can make some function that does what you want with the syntax you want:
extension Observable {
func flatMapLatest(condition: #escaping (E) -> Bool, then: #escaping (E) -> Observable, otherwise: #escaping () -> Observable) -> Observable {
let observable = self.shareReplayLatestWhileConnected()
let observableCondition = observable.map({ condition($0) }).shareReplayLatestWhileConnected()
let observableThen: Observable<E> = observableCondition
.filter({ $0 })
.withLatestFrom(observable)
.flatMapLatest({ then($0) })
.shareReplayLatestWhileConnected()
let observableOtherwise: Observable<E> = observableCondition
.filter({ !$0 })
.withLatestFrom(observable)
.flatMapLatest({ _ in otherwise() })
.shareReplayLatestWhileConnected()
return Observable<Observable<E>>
.from([observableThen, observableOtherwise])
.merge()
}
}
and use it
func refresh() -> Observable<Status> {
let condition = { (status: Status) -> Bool in
return status == .exists
}
let then = { (status: Status) -> Observable<Status> in
return Observable.just(status)
}
return checkLocalStatus()
.flatMapLatest(condition: condition, then: then, otherwise: self.attemptRemoteStatusOverride)
.flatMapLatest(condition: condition, then: then, otherwise: self.attemptRemoteStatusUpdate)
}

session.dataTask synchronization issue

I have some trouble with the code below.
Though it works, there is some timing problem.
First let me say what I expect, I suppose the completion handler should be run when the data download is complete and my image ready to use. But reality seems to be quite different. When I try it the completion handler is called right away (I can see 'All OK' in the console) as if everything was instantaneous. But the image gets actually displayed much later. What am I missing?
let imageURL = URL(string: myURLString)
session = URLSession.shared,
_ = session.dataTask(with: imageURL) {[weak self]
(data: Data?, response: URLResponse?, error: Error?) in
if error == nil {
print("All OK")
self?.theImage = UIImage(data: data!)
self?.theView.image = self?.theImage
} else {print(error!)}
DispatchQueue.main.async {
self?.activityIndicator.stopAnimating()
self?.theView.setNeedsDisplay()
}
}.resume()
Can you try this code?
The control should not be actually going inside the handler at first call. And I think there are a few mistakes in your code as well which I pointed out earlier, especially the main thread is required for updating UI.
let session : URLSession
let config = URLSessionConfiguration.default
var resultFromServer: Any?
let responseResultData = [String:Any]()
session = URLSession(configuration: config, delegate: nil, delegateQueue: nil)
session.dataTask(with: request) { (data, response, error ) in
if error != nil {
DispatchQueue.main.async(execute: {
session.invalidateAndCancel()
})
}else{
let httpResponse: HTTPURLResponse = response as! HTTPURLResponse
do{
resultFromServer = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers)
if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 || httpResponse.statusCode == 202 || httpResponse.statusCode == 204 || httpResponse.statusCode == 203 {
if let respArr = resultFromServer as? [Any]{
//resp is array
}else if let respdict = resultFromServer as? [String : Any] {
//resp is dict
}else{
//resp is something else maybe string, etc
}
}
else {
//error status code something like 500, 404, etc
}
}
catch let error as NSError {
DispatchQueue.main.async(execute: {
session.invalidateAndCancel()
})
}
}
session.finishTasksAndInvalidate()
}.resume()

Formating json object into Array of dictionary in swift

let todoEndpoint: String ="http://localhost:16789/api/product"
guard let url = NSURL(string: todoEndpoint) else {
print("Error: cannot create URL")
return
}
let urlRequest = NSURLRequest(url: url as URL)
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
let task = session.dataTask(with: urlRequest as URLRequest) {
(data, response, error) in
guard let responseData = data else {
print("Error: did not receive data")
return
}
guard error == nil else {
print("error calling GET")
print(error)
return
}
do{
self.users = try JSONSerialization.jsonObject(with: responseData, options: []) as? Array}
catch{
print("Error parsing")
}
}
task.resume()
this is my Output my API
[{"ProductId":2,"Category":"Laptop","Brand":"Apple","ModelName":"MacBook Pro","SerialNumber":"Test1","DatePurchased":"2016-08-12T00:00:00","EmployeeName":"Vismita Shetty","DateAssigned":"2017-01-17T00:00:00"},{"ProductId":4,"Category":"Keyboard","Brand":"Logitech","ModelName":"K200","SerialNumber":"TestKeyboard1","DatePurchased":"2017-01-23T03:02:13.247","EmployeeName":"Vismita Shetty","DateAssigned":"2017-01-17T00:00:00"},{"ProductId":5,"Category":"Keyboard","Brand":"Logitech","ModelName":"K200","SerialNumber":"testkeyboard2","DatePurchased":"2017-01-23T03:03:26.07","EmployeeName":"Suraj Pangam","DateAssigned":"2017-01-17T00:00:00"}]
What I currently trying to do convert user into different variable which will be of type Array of dictionaries in swift. So that Each object can be accessed by key. I want to make it generic so that even some properties got changed it won't matter as it won't be accessed by providing property name. Like (users[indexPath.row] as? [String: AnyObject])?["EmployeeName"] as! String!.
Instead if i make it to dictionary i will be easily do it by running for (key,value) loop for array of dictionary.

Alamofire Decodable serializer

I am trying to update some code for a Alamofire custom response serializer I found this bit of code on "bits of cocoa".
extension Alamofire.Request {
public func responseCollection<T: Decodable>(completionHandler: Response<[T], NSError> -> Void) -> Self {
let responseSerializer = ResponseSerializer<[T], NSError> { request, response, data, error in
guard error == nil else { return .Failure(error!) }
let result = Alamofire
.Request
.JSONResponseSerializer(options: .AllowFragments)
.serializeResponse(request, response, data, error)
switch result {
case .Success(let value):
do {
return .Success(try [T].decode(value))
} catch {
return .Failure(Error.errorWithCode(.JSONSerializationFailed,
failureReason: "JSON parsing error, JSON: \(value)"))
}
case .Failure(let error): return.Failure(error)
}
}
return response(responseSerializer: responseSerializer, completionHandler: completionHandler)
}
}
This is pre swift 3, and Response<[T], NSError> is now a single value specialization Response<[T]> because of this I am not sure how this extension would translate for the changes to Alamofire on the swift 3
I started to update this code this is as far as I got
extension Alamofire.Request {
public func responseCollection<T: Decodable>(completionHandler: (Response<[T]>) -> Void) -> Self {
let responseSerializer = ResponseSerializer<[T]> { request, response, data, error in
guard error == nil else { return .failure(error!) }
let result = Alamofire
.Request
.JSONResponseSerializer(options: .allowFragments)
.serializeResponse(request, response, data, error)
switch result {
case .success(let value):
do {
return .success(try [T].decode(value))
} catch {
return .failure(Error(.errorWithCode(.JSONSerializationFailed, failureReason: "JSON parsing error, JSON: \(value)")))
}
case .failure(let error): return.failure(error)
}
}
return response(responseSerializer: responseSerializer, com
pletionHandler: completionHandler)
}
}
this get me 2 errors that at the moment I have not found any way to fix them:
1) for "return .failure(Error(.errorWithCode(.JSONSerializationFailed, failureReason: "JSON parsing error, JSON: \(value)")))", I am getting this error ('Error' cannot be constructed because it as no accessible initializers)
2) for "return response(responseSerializer: responseSerializer, completionHandler: completionHandler)", I am getting this error (Cannot call value of non-function type 'HTTPURLResponse')
Hopefully if any one can point me to a better solution then this bit of code or the correct fix for this. Thanks I will be working on this still, if I do fix it I will update this ticket.
Edit - update
So This is the code as of now
extension Alamofire.Request {
public func responseDecodable<T: Decodable>(completionHandler: #escaping (Response<T>) -> Void) -> Self {
let responseSerializer = ResponseSerializer<T> { request, response, data, error in
guard error == nil else {
print("error Network request: \(error)")
return .failure(error!)
}
let result = Alamofire
.Request
.JSONResponseSerializer(options: .allowFragments)
.serializeResponse(request, response, data, error)
switch result {
case .success(let value):
do {
let decodableObject = try T.decode(value)
return .success(decodableObject)
} catch let decodeErr {
print(decodeErr)
let failureReason = "JSON parsing error, JSON: \(value)"
let userInfo = [NSLocalizedFailureReasonErrorKey: failureReason]
let error = NSError(domain: "com.prospects.error", code: BackendError.JSONSerializationFailed.rawValue, userInfo: userInfo)
return .failure(error)
}
case .failure(let error): return.failure(error)
}
}
return response(responseSerializer: responseSerializer, completionHandler: completionHandler)
}
}
public protocol ResponseObjectSerializable {
init?(response: HTTPURLResponse, representation: AnyObject)
}
as per last comment : Response and ResponseSerializer are now unresolved, this used to work with no error yesterday. but updating xcode and alamofire this morning as made this to get errors now.

Best practice to safely load image from url

I have the following code snippet to load an image from an url:
let url = NSURL(string: imageUrl)
let data = NSData(contentsOfURL: url!)
let image = UIImage(data: data!)
In case that my variable imageUrl has a valid string value, what is the most secure way to protect this code against possible edge cases?
Following code seems not to be very handy:
if let url = NSURL(string: imageUrl) {
if let data = NSData(contentsOfURL: url) {
if let image = UIImage(data: data) {
// success -> do something with the image...
}
else {
// print error message
}
}
else {
// print error message
}
}
else {
// print error message
}
The best practice is not to use a synchronous method like contentsOfURL to load data from over the network.
The recommended way is NSURLSession which works asynchronously.
This is a simple example with a completion block and an enum with associated types,
it catches all possible errors
enum Result {
case Success(UIImage), Failure(NSString)
}
func loadImage(string : String, completion: (Result) -> ()) {
guard let url = NSURL(string: string) else {
completion(.Failure("Bad URL"))
return
}
NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) in
if error != nil {
completion(.Failure(error!.localizedDescription))
} else {
guard let image = UIImage(data: data!) else {
completion(.Failure("Could not load image data"))
return
}
completion(.Success(image))
}
}.resume()
}
Call it with:
loadImage("http://myserver.com/path/to/image.png") { result in
switch result {
case .Success(let image) :
// do something with the image
case .Failure(let error) :
print(error)
}
}