Wait for GET request completed - swift

I am trying to figure out how can I tell the application to wait until one task going to finish. I need to do two rest request and they depends of each other. The second one need data from first one to be successfully.
So the way I am doing my test request is :
func get(completion: #escaping (Any?) -> Void){
Alamofire.request(URL, method: .get, parameters: ["token": TOKEN]).validate().responseJSON { response in
if response.result.value != nil {
// DESERIALIZE IT INTO ARRAY " objects"
}
completion(objects)
} else {
completion(nil)
}
}
}
and the second get which needs the data from first looking simmilar like this one - only the change is the URL.
Then I would like to invoke both functions somewhere in my application and do smth with deserialized data.
func loadData() {
Service.Instance.get{ (json) in
guard json != nil else {
print("Error / json = nil pointer")
return
}
self.data = json as! [Data]
}
for dt in self.data {
Service.Instance.get(data: dt, offset: 0, limit: 10, completion: { (json2) in
guard json2 != nil else {
print("Error / json = nil pointer")
return
}
if dt.count > 0 {
self.objects = json2 as! [VehicleModel]!
self.fulldata.append(self.objects)
} else {
print("There is no object in the Array")
}
})
}
}
So I would like to tell the application to wait for first request finished before starts next cuz it will crash with nil in for loop.

Related

In SwiftUI, how can return a function only when an API request is finished? [duplicate]

This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 1 year ago.
I'm trying to learn a bit about making API calls in SwiftUI.
I have a function called loadData which runs via the onAppear modifier.
The goal of that function is to see if I currently have data in CoreData.
If there is no data in CoreData, then I'd like to call another function that makes the API call to get the data, but only return the fetched data.
With the example I have below, the getCurrentSol function returns before the async portion is finished. Resulting in no data being returned. What is the appropriate way for me to return the data?
As you can see, I did try a while(true) "trick". But for whatever reason, my results variable never even updates with the fetched data, even though the decodedData variable does contain the proper results.
}.onAppear(perform: loadData)
}
func loadData() {
print("data: \(storedData) ")
print("data.count: \(storedData.count)")
if(storedData.count == 0){
let fetchedData = getCurrentSol()
let currentSol = fetchedData.sol
print("fetchedData: \(fetchedData)")
print("currentSol: \(currentSol)")
}
}
func getCurrentSol() -> CuriosityRoverModel {
var results = CuriosityRoverModel(sol: 0, low: 0, high: 0, opacity: "Sunny", sunrise: "00:00", sunset: "00:00", month: "Month 0")
let urlString = "https://api.maas2.apollorion.com"
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!) {data, response, error in
DispatchQueue.main.async {
if let data = data {
do {
let decoder = JSONDecoder()
let decodedData = try decoder.decode(CuriosityRoverModel.self, from: data)
//This recieves the proper data, but it doesn't get written to the results var
print("decodedData: \(decodedData)")
results = decodedData
} catch {
print("Error: ", error)
}
}
}
}.resume()
// I thought this would be a way to wait for the data
// but results never gets updated so it ends up running endlessly
while(true){
if(results.sol > 0){
return results
}
}
//return results // This would just return the "empty" results var from above before the data is actually retrieved
}
}
There are many ways to achieve what you want. This is one approach, using a closure:
....
.onAppear(perform: loadData)
}
func loadData() {
print("data: \(storedData) ")
print("data.count: \(storedData.count)")
if (storedData.count == 0) {
getCurrentSol() { results in // <--- here
if let fetchedData = results {
let currentSol = fetchedData.sol
print("fetchedData: \(fetchedData)")
print("currentSol: \(currentSol)")
}
}
}
}
// use a completion closure to "return" your results when done, not before
func getCurrentSol(completion: #escaping (CuriosityRoverModel?) -> Void) {
let urlString = "https://api.maas2.apollorion.com"
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!) {data, response, error in
DispatchQueue.main.async {
if let data = data {
do {
let decoder = JSONDecoder()
let decodedData = try decoder.decode(CuriosityRoverModel.self, from: data)
print("decodedData: \(decodedData)")
completion(decodedData) // <--- here, return the results
} catch {
print("Error: ", error) // need to deal with errors
completion(nil) // <--- here, should return the error
}
}
}
}.resume()
}
}

Using completion handlers inside while loop

How to use completion handlers/dispatchqueue within while loops?
I have this method called getHub() which is a completion handler as I would like code to be executed after it has finished with the relevant values. I call this when a user presses a button:
SetupAPI().getHub(completion: { response, error in
print("testing")
print(response)
print(error)
})
(The code above is where all of the code below should end at)
It calls my API and if the API returns an error/a value that I wasn't expecting, or if Almofire couldn't do the request for some reason, then it adds one onto the tries variable. The max amount of tries allowed is 3 given by the maxTries variable. If the tries variable is equal to the maxTries variable, then a bool timeout is set to true. If the tries variable is below the maxTries variable then the code waits timeoutInSeconds - which is 10 seconds - amount of time before exiting the while loop, which should run the code once more.
Similarly, If the right value is returned from fetching the data from my API then a bool found is set to true.
If either of these variables are true, then the while loop breaks. And an error is sent back to the completion handler for the code above (which then allows me to tell the user that something has gone wrong).
However, when I run it, the completion handler above is not finished, and the code just runs through the while loop and called function over and over again as my console fills with starting and fetching via my two print statements for debugging in below code. What's the problem, can I use a DispatchQueue/ completion handlers in this situation?
Function that gets called via above code:
func getHub(completion: #escaping (Bool, Error?) -> Void) {
var tries = 0
let maxTries = 3
let timeoutInSeconds = 10.0
var found = false
var timeout = false
while !found || !timeout{
print("starting")
getHubCallAPI(completion: {status, error in
if(error == nil){
print(status)
if (status == "No documents found"){
if(tries >= maxTries){
print("Tired too many times")
timeout = true
return completion(false, nil)
}
tries += 1
DispatchQueue.main.asyncAfter(deadline: .now() + timeoutInSeconds){
return
}
}else{
found = true
print("Hub found")
return completion(true, nil)
}
}else{
print("error")
return completion(false, error)
}
})
}
}
Function that calls the API and returns it back to the function above ^^:
func getHubCallAPI(completion: #escaping (String, Error?) -> Void) {
print("fetching")
AF.request("https://discovery.ellisn.com", encoding: URLEncoding.default).response { response in
print("Request: \(response.request)")
print("Response: \(response.response)")
print("Error: \(response.error)")
if(response.error != nil){
return completion("", response.error)
}
if let data = response.data, let status = String(data: data, encoding: .utf8) {
return completion(status, nil)
}
}
}
Any questions, or more clarification needed, then just ask. Thanks.
You can try the following:
func getHub(triesLeft: Int = 3, completion: #escaping (Bool, Error?) -> Void) {
let timeoutInSeconds = 1.0
print("starting")
getHubCallAPI(completion: { status, error in
if error == nil {
print(status)
if status != "No documents found" {
print("Hub found")
return completion(true, nil)
}
} else {
print("error")
return completion(false, error) // comment out if the loop should continue on error
}
if triesLeft <= 1 {
print("Tried too many times")
return completion(false, nil)
}
DispatchQueue.main.asyncAfter(deadline: .now() + timeoutInSeconds) {
getHub(triesLeft: triesLeft - 1, completion: completion)
}
})
}
And just call it once like this:
getHub(triesLeft: 2, completion: { ... })
Note that unless you need it for some other reason, there is no need to return (Bool, Error?). And the second parameter is always nil - you may want to propagate your error. You could in theory return (String?, Error?).

How do I use RxSwift with AlamoFire and SwiftyJSON?

I'm trying to learn RxSwift and currently I'm trying to use it in relation to AlamoFire and SwiftyJSON, that is, to observe when JSON has been downloaded so that I can parse it. I have working code for getting JSON:
guard let myURL = URL(string: "https://api.myjson.com/bins/e5gjk") else { return }
var myArray = [People]()
let myObserver = Observable.from(myArray)
Alamofire.request(myURL, method: .get)
.validate()
.responseJSON{ response in
guard response.result.isSuccess else {
print("Error")
return
}
let json = JSON(response.result.value)
for i in 0...json["employees"].count {
let people = People()
people.name = json["employees"][i]["firstName"].stringValue
people.job = json["employees"][i]["job"].stringValue
myArray.append(people)
}
for i in myArray {
print(i.name)
print(i.job)
}
}
myObserver.subscribe(onNext: {
print($0)
}, onError: { error in
print(error)
}, onCompleted: {
print("completed")
}, onDisposed: {
print("disposed")
}).disposed(by: DisposeBag())
As you can see, I have parsed the JSON as well. I guess that the point of RX here would be to use the data in onNext once it has been parsed, correct? Or have I misunderstood its purpose?
Anyway, I have an observer on myArray: let myObserver = Observable.from(myArray). In my head, subscribe onNext should be triggered as soon as myArray gets data, but that's not happening. What happens is that completed is run immediately, then the JSON networking and parsing takes place. The subscription is not triggered when myArray gets new data. Have I missed something or misunderstood RX's purpose?
EDIT
Or wait, should the whole JSON handling and parsing be in the onNext?
you need to create your observer. This should work :
let observer = Observable<People>.create { (observer) -> Disposable in
Alamofire.request(myURL, method: .get)
.validate()
.responseJSON { response in
guard response.result.isSuccess else {
print("Error")
observer.on(.error(response.result.error!))
return
}
let json = JSON(response.result.value)
for i in 0...json["employees"].count {
let people = People()
people.name = json["employees"][i]["firstName"].stringValue
people.job = json["employees"][i]["job"].stringValue
observer.on(.next(people))
myArray.append(people)
}
observer.on(.completed)
for i in myArray {
print(i.name)
print(i.job)
}
}
return Disposables.create()
}
then you can subscribe to your observer of type Observable<People>
observer.subscribe { (event) in
switch event {
case .next(let people):
print(people.job)
print(people.name)
case .error(let error):
print("error \(error.localizedDescription)")
case .completed:
print("completed")
}
}.disposed(by: disposeBag)

Sorting JSON when using Alamofire and SwiftyJSON

Morning all,
I've been following along the examples in the excellent iOS Apps With REST APIs book and as a result using Alamofire and SwiftyJSON. One thing I don't see mentioned in the book is how you would sort the incoming json objects into a specific order, eg. date. The code I've pasted below works fine for pulling in the json objects however as far as I can tell, they're automatically ordered by created_by. I'd like to sort by a different order, lets say my class was called Vegetable and had a name attribute so that I could sort by something like:
.sort { $0.name < $1.name }
I'll start with the Vegetable class in Vegetable.swift
class Vegetable: ResponseJSONObjectSerializable {
var id: Int?
var name : String?
var date: NSDate?
}
Inside my JSONSerializer file I have the following, I'm not sure I'd wish to change the order directly in here as I'd prefer some more flexibility with each call.
public func responseArray<T: ResponseJSONObjectSerializable>(completionHandler: Response<[T], NSError> -> Void) -> Self {
let serializer = ResponseSerializer<[T], NSError> { request, response, data, error in
guard error == nil else {
return .Failure(error!)
}
guard let responseData = data else {
let failureReason = "Array could not be serialized because input data was nil."
let error = Error.errorWithCode(.DataSerializationFailed, failureReason: failureReason)
return .Failure(error)
}
let JSONResponseSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
let result = JSONResponseSerializer.serializeResponse(request, response, responseData, error)
switch result {
case .Success(let value):
let json = SwiftyJSON.JSON(value)
var objects: [T] = []
for (_, item) in json {
if let object = T(json: item) {
objects.append(object)
}
}
return .Success(objects)
case .Failure(let error):
return .Failure(error)
}
}
return response(responseSerializer: serializer, completionHandler: completionHandler)
}
Then, in my APIManager I have the following function
func getAllVegetables(completionHandler: (Result<[Vegetable], NSError>) -> Void) {
Alamofire.request(VegetableRouter.GetVegetables())
.responseArray { (response:Response<[Vegetable], NSError>) in
completionHandler(response.result)
}
}
Finally, populate my tableview I have:
func loadVegetables() {
self.isLoading = true
VegetablesAPIManager.sharedInstance.getAllVegetables() {
result in
self.isLoading = false
if self.refreshControl.refreshing {
self.refreshControl.endRefreshing()
}
guard result.error == nil else {
print(result.error)
// TODO: Display Error
return
}
if let fetchedVegetables = result.value {
self.vegetables = fetchedVegetables
for vegetable in fetchedVegetables {
// Nothing here at the moment
}
}
self.tableView.reloadData()
}
}
I appreciate any help I can get with this, Thanks!
Since you have a NSDate property, you can sort with the compare method of NSDate.
let sorted = result.value.sort { $0.date.compare($1.date) == .OrderedAscending }

Return data out of http sendAsyncRequest call in swift

Ok so first of all, I've read several other post on the subject and I know that what Im going to describe could be a synchronous call but what I really need is to get the data and work with it, so if I should look in some other direction let me know:
I have to make calls to an api to retrieve json objects and use them to populate a table view. my problem is that I can't manage to passĀ / return the data to work with it:
in the controller:
callProjectTypes()
var testjson = JSON(data:test)
println("should print some data")
println(testjson[0])
the call functions:
func callProjectTypes() -> (NSData) {
var data = NSData()
serverCall("http://url/to/project/types") {
responseData, error in
if responseString == nil {
println("Error during post: \(error)")
return
}
// use responseData here
data = responseData
}
return data
}
func serverCall(url: String, completionHandler: (responseData: NSData!, error: NSError!) -> ()) {
var URL: NSURL = NSURL(string: url)!
var request:NSMutableURLRequest = NSMutableURLRequest(URL:URL)
request.HTTPMethod = "GET";
var creds = encodeCredentials()
request.addValue("\(creds)", forHTTPHeaderField: "Authorization")
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()){
response, data, error in
var output: NSData!
if data != nil {
output = data
}
completionHandler(responseString: output, error: error)
}
}
println(testjson[0]) always return "nill" so I assume the "return data" from my callProjectTypes function is coming back before the data arrived.
What should I do to make this work?
To return values asynchronously you cannot use a return statement because you have no guarantee that 'serverCall' will have finished when the return statement is executed. Instead, you have to return the 'data' value in an asynchronous fashion, such as by providing a callback to 'callProjectTypes' itself. Example:
callProjectTypes()
{
(data : NSData) in
let testjson = JSON(data:data)
println("should print some data")
println(testjson[0])
}
The async function must take and execute a callback:
func callProjectTypes(callback: (data : NSData)->Void)
{
serverCall("http://url/to/project/types") {
responseData, error in
callback(responseData)
}
Now you can guarantee that the code in callback will only be executed after the data has been returned by 'serverCall'.
You cant return that way using async.
You have to do everything you need with the data inside the closure.
You have to do this:
func callProjectTypes(action: NSData->()) {
serverCall("http://url/to/project/types") { responseData, error in
if responseData == nil {
println("Error during post: \(error)")
return
}
action(responseData)
}
}
callProjectTypes { data in
let testjson = JSON(data:data)
println("should print some data")
println(testjson[0])
}