How to modify a struct with async callbacks? - swift

I'm trying to update a struct with multi-level nested async callback, Since each level callback provides info for next batch of requests till everything is done. It's like a tree structure. And each time I can only get to one level below.
However, the first attempt with inout parameter failed. I now learned the reason, thanks to great answers here:
Inout parameter in async callback does not work as expected
My quest is still there to be solved. The only way I can think of is to store the value to a local file or persistent store and modify it directly each time. And after writing the sample code, I think a global var can help me out on this as well. But I guess the best way is to have a struct instance for this job. And for each round of requests, I store info for this round in one place to avoid the mess created by different rounds working on the same time.
With sample code below, only the global var update works. And I believe the reason the other two fail is the same as the question I mentioned above.
func testThis() {
var d = Data()
d.getData()
}
let uriBase = "https://hacker-news.firebaseio.com/v0/"
let u: [String] = ["bane", "LiweiZ", "rdtsc", "ssivark", "sparkzilla", "Wogef"]
var successfulRequestCounter = 0
struct A {}
struct Data {
var dataOkRequestCounter = 0
var dataArray = [A]()
mutating func getData() {
for s in u {
let p = uriBase + "user/" + s + ".json"
getAnApiData(p)
}
}
mutating func getAnApiData(path: String) {
var req = NSURLRequest(URL: NSURL(string: path)!)
var config = NSURLSessionConfiguration.ephemeralSessionConfiguration()
var session = NSURLSession(configuration: config)
println("p: \(path)")
var task = session.dataTaskWithRequest(req) {
(data: NSData!, res: NSURLResponse!, err: NSError!) in
if let e = err {
// Handle error
} else if let d = data {
// Successfully got data. Based on this data, I need to further get more data by sending requests accordingly.
self.handleSuccessfulResponse()
}
}
task.resume()
}
mutating func handleSuccessfulResponse() {
println("successfulRequestCounter before: \(successfulRequestCounter)")
successfulRequestCounter++
println("successfulRequestCounter after: \(successfulRequestCounter)")
println("dataOkRequestCounter before: \(dataOkRequestCounter)")
dataOkRequestCounter++
println("dataOkRequestCounter after: \(dataOkRequestCounter)")
println("dataArray count before: \(dataArray.count)")
dataArray.append(A())
println("dataArray count after: \(dataArray.count)")
if successfulRequestCounter == 6 {
println("Proceeded")
getData()
}
}
}
func getAllApiData() {
for s in u {
let p = uriBase + "user/" + s + ".json"
getOneApiData(p)
}
}
Well, in my actual project, I successfully append a var in the struct in the first batch of callbacks and it failed in the second one. But I failed to make it work in the sample code. I tried many times so that it took me so long to update my question with sample code. Anyway, I think the main issue is to learn appropriate approach for this task. So I just put it aside for now.
I guess there is no way to do it with closure, given how closure works. But still want to ask and learn the best way.
Thanks.

What I did was use an inout NSMutableDictionary.
func myAsyncFunc(inout result: NSMutableDictionary){
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
let intValue = result.valueForKey("intValue")
if intValue as! Int > 0 {
//Do Work
}
}
dispatch_async(dispatch_get_main_queue()) {
result.setValue(0, forKey: "intValue")
}
}
I know you already tried using inout, but NSMutableDictionary worked for me when no other object did.

Related

Fetching API data asynchronously and making calls every 1 second

I have a function:
func fetchHistoricalCurrencyData(completion: #escaping (() -> ())) {
let g = DispatchGroup()
DispatchQueue.main.async { [weak self] in
var historicalCurrencyDataTemp = [HistoricalCurrencyDataModel]()
let dayDurationInSeconds: TimeInterval = 60*60*24
for date in stride(from: self!.startingDate, to: self!.endingDate, by: dayDurationInSeconds) {
g.enter()
APIManager.shared.getHistoricalCurrencyData(date: self!.convertDateToStringDate(date: date), fromCurrency: String(self!.fromCurrency.prefix(3)), amount: self!.amount, toCurrency: String(self!.toCurrency.prefix(3))) {
historicalCurrencyDataModel in
historicalCurrencyDataTemp.append(historicalCurrencyDataModel)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
g.leave()
}
}
}
g.notify(queue:.main) {
self?.historicalCurrencyData = DataManager.shared.getHistoricalCurrencyData(historicalCurrencyDataModels: historicalCurrencyDataTemp)
print(self?.historicalCurrencyData)
completion()
}
}
}
What's more, I have a dateRange which I divide into particular days within this range by using stride() function. Every day I get from this function I use to make an API Call to get desired data. Every API data I get is being converted to particular HistoricalCurrencyDataModel and appended to temp array which I create before the loop (historicalCurrencyDataTemp). When I receive all the data I want I use DataManager class to transform this data into [String: Double] dictionary which I finally assign to my #Published var historicalCurrencyData: [String: Double] variable.
One clue thing is that API I use make it possible to make API calls one after another at least after 1 second so I have to create 1 second delay between each call. I made it possible with DispatchQueue.main.asyncAfter(deadline: .now() + 1) statement. I control the whole flow of asynchronous operations with DispatchGroup().
Unfortunately after all of this what I get is empty Optional([:]) within print(self?.historicalCurrencyData) statement. And for example the correct behaviour would be that if I use dateRange with 5 days I get a dictionary with 5 entries.
I ultimately got rid of all DispatchQueues and now it works as it should. I believe there was something wrong with the delay and not all values were fetched correctly. What's more, I discovered that there is no need of creating 1 second between API calls I thought otherwise after reading API documentation. I must have been wrong.
As suggested (which I agree with) I attach working code:
func fetchHistoricalCurrencyData(completion: #escaping (() -> ())) {
let g = DispatchGroup()
var historicalCurrencyDataTemp = [HistoricalCurrencyDataModel]()
for date in stride(from: self.startingDate, to: self.endingDate, by: dayDurationInSeconds) {
g.enter()
APIManager.shared.getHistoricalCurrencyData(date: self.convertDateToStringDate(date: date), fromCurrency: String(self.fromCurrency.prefix(3)), amount: self.amount, toCurrency: String(self.toCurrency.prefix(3))) {
historicalCurrencyDataModel in
historicalCurrencyDataTemp.append(historicalCurrencyDataModel)
g.leave()
}
}
g.notify(queue:.main) {
self.historicalCurrencyData = DataManager.shared.getHistoricalCurrencyData(historicalCurrencyDataModels: historicalCurrencyDataTemp)
print(self.historicalCurrencyData)
completion()
}
}

Returning parsed JSON data using Alamofire?

Hello new to Swift and Alamofire,
The issue i'm having is when I call this fetchAllUsers() the code will return the empty users array and after it's done executing it will go inside the AF.request closure and execute the rest.
I've done some research and I was wondering is this is caused by Alamofire being an Async function.
Any suggestions?
func fetchAllUsers() -> [User] {
var users = [User]()
let allUsersUrl = baseUrl + "users/"
if let url = URL(string: allUsersUrl) {
AF.request(url).response { response in
if let data = response.data {
users = self.parse(json: data)
}
}
}
return users
}
You need to handle the asynchrony in some way. This this means passing a completion handler for the types you need. Other times it means you wrap it in other async structures, like promises or a publisher (which Alamofire also provides).
In you case, I'd suggest making your User type Decodable and allow Alamofire to do the decoding for you.
func fetchAllUsers(completionHandler: #escaping ([User]) -> Void) {
let allUsersUrl = baseUrl + "users/"
if let url = URL(string: allUsersUrl) {
AF.request(url).responseDecodable(of: [User].self) { response in
if let users = response.value {
completionHandler(users)
}
}
}
}
However, I would suggest returning the full Result from the response rather than just the [User] value, otherwise you'll miss any errors that occur.

Can't get data returned from dataTask()

For one week I have been trying to get a string returned from dataTask().
I already read a lot here on StackOverFlow and also from serval sites where they tackle this topic. For example, this one. So I already understand that it's that the dataTask doesn't directly return values, cause it happens on different threads and so on. I also read about closures and completion handlers. I really got the feeling that I actually already got a little clue what this is about. But I can't get it to work.
So this is my code. I just post the whole code so no-one needs to worry that the problem sticks in a part which I don't show. Everything is working fine until I try to return a value and save it for example in a variable:
func requestOGD(code gtin: String, completion: #escaping (_ result: String) -> String) {
// MARK: Properties
var answerList: [String.SubSequence] = []
var answerDic: [String:String] = [:]
var product_name = String()
var producer = String()
// Set up the URL request
let ogdAPI = String("http://opengtindb.org/?ean=\(gtin)&cmd=query&queryid=400000000")
guard let url = URL(string: ogdAPI) else {
print("Error: cannot create URL")
return
}
let urlRequest = URLRequest(url: url)
// set up the session
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
// make the request
let task = session.dataTask(with: urlRequest) {
(data, response, error) in
// check for any errors
guard error == nil else {
print("error calling GET on /todos/1")
print(error!)
return
}
// make sure we got data
guard let responseData = data else {
print("Error: did not receive data")
return
}
// parse the result, which is String. It willbecome split and placed in a dictionary
do {
let answer = (String(decoding: responseData, as: UTF8.self))
answerList = answer.split(separator: "\n")
for entry in answerList {
let entry1 = entry.split(separator: "=")
if entry1.count > 1 {
let foo = String(entry1[0])
let bar = String(entry1[1])
answerDic[foo] = "\(bar)"
}
}
if answerDic["error"] == "0" {
product_name = answerDic["detailname"]!
producer = answerDic["vendor"]!
completion(product_name)
} else {
print("Error-Code der Seite lautet: \(String(describing: answerDic["error"]))")
return
}
}
}
task.resume()
Here I call my function, and no worries, I also tried to directly return it to the var foo, also doesn't work The value only exists within the closure:
// Configure the cell...
var foo:String = ""
requestOGD(code: listOfCodes[indexPath.row]) { (result: String) in
print(result)
foo = result
return result
}
print("Foo:", foo)
cell.textLabel?.text = self.listOfCodes[indexPath.row] + ""
return cell
}
So my problem is, I have the feeling, that I'm not able to get a value out of a http-request.
You used a completion handler in your call to requestOGD:
requestOGD(code: listOfCodes[indexPath.row]) {
(result: String) in
// result comes back here
}
But then you tried to capture and return that result:
foo = result
return result
So you're making the same mistake here that you tried to avoid making by having the completion handler in the first place. The call to that completion handler is itself asynchronous. So you face the same issue again. If you want to extract result at this point, you would need another completion handler.
To put it in simple terms, this is the order of operations:
requestOGD(code: listOfCodes[indexPath.row]) {
(result: String) in
foo = result // 2
}
print("Foo:", foo) // 1
You are printing foo before the asynchronous code runs and has a chance to set foo in the first place.
In the larger context: You cannot use any asynchronously gathered material in cellForRowAt. The cell is returned before the information is gathered. That's what asynchronous means. You can't work around that by piling on further levels of asynchronicity. You have to change your entire strategy.

RxSwift Using Variables Correctly

I am trying to convert a project to use RxSwift and MVVM. I have a service that syncs a list of data from Parse on every app launch and I basically want to make sure I am taking the correct approach.
What I have done is made a Variable subject and then allow my models to listen to this.
ParseService:
let rx_parseMushrooms = Variable<[ParseMushroom]>([])
MushroomLibraryModel:
_ = parseService.rx_parseMushrooms
.asObservable()
.map { (parseMushrooms:[ParseMushroom]) -> [Mushroom] in
let mushrooms = parseMushrooms.map { (parseMushroom:ParseMushroom) -> Mushroom in
let mushroom = Mapper<Mushroom>().map(parseMushroom.dictionaryWithValuesForKeys(parseMushroom.allKeys()))
return mushroom!
}
return mushrooms
}
.subscribeNext({ (mushrooms:[Mushroom]) -> Void in
self.mushrooms = mushrooms
print(mushrooms)
})
I do the same for expressing the sync state.
ParseService:
struct SyncState {
enum State {
case Unsynced, ConnectingToServer, SyncingInfo, FetchingImageList, SyncingImages, SyncComplete, SyncCompleteWithError
}
var infoToSync = 0
var imagesToSync = 0
var imagesSynced = 0
var state = State.Unsynced
}
let rx_syncState = Variable(SyncState())
I then update the variable a la
self.rx_syncState.value = self.syncState
SyncViewModel:
_ = parseService.rx_syncState
.asObservable()
.subscribeNext { [weak self] (syncState:ParseService.SyncState) -> Void in
switch syncState.state {
//show stuff based on state struct
}
}
Anyways, I would greatly appreciate if someone can tell me if this is a good way of going about it or if I am misusing RxSwift (and guide me on how I should be doing this).
Cheers!
Hmm... Here is an article about using Variable (note that Variable is a wrapper around BehaviorSubject.)
http://davesexton.com/blog/post/To-Use-Subject-Or-Not-To-Use-Subject.aspx
In your case, you already have a cold observable (the network call,) so you don't need a Subject/Variable. All you need to do is publish the observable you already have and use replay(1) to cache the value. I would expect a class named something like ParseServer that contains a computed property named something like mushrooms.
To help get the mushrooms out of parse, you could use this (this will create the cold observable you need):
extension PFQuery {
var rx_findObjects: Observable<[PFObject]> {
return Observable.create { observer in
self.findObjectsInBackgroundWithBlock({ results, error in
if let results = results {
observer.on(.Next(results))
observer.on(.Completed)
}
else {
observer.on(.Error(error ?? RxError.Unknown))
}
})
return AnonymousDisposable({ self.cancel() })
}
}
}
And then you would have something like:
class ParseServer {
var mushrooms: Observable<[Mushroom]> {
return PFQuery(className: "Mushroom").rx_findObjects
.map { $0.map { Mushroom(pfObject: $0) } }
.publish()
.replay(1)
}
}
I think the above is correct. I didn't run it through a compiler though, much less test it. It might need editing.
The idea though is that the first time you call myParseServer.mushrooms the system will call Parse to get the mushrooms out and cache them. From then on, it will just return the previous cashed mushrooms.

How i can return value from function use Alamofire

How i can return value from function used Alamofire . i try to print outside .responseJSON the value in ArrData is not set but i try print inside it work
this code:
func getDept()->NSMutableArray
{
var ArrData:NSMutableArray = []
let url = "http://www.xxxxxxxxxxxxx.com"
Alamofire.request(.GET, url).responseJSON { response in
let json = JSON(response.result.value!)
let count = json.count
for var index = 0; index < count;index++
{
ArrData.addObject(json[index]["dept"].stringValue)
}
}
return ArrData
}
it i good idea to check at least README.md of the framework which you are going to use in your code
Networking in Alamofire is done asynchronously. Asynchronous
programming may be a source of frustration to programmers unfamiliar
with the concept, but there are very good reasons for doing it this
way.
Rather than blocking execution to wait for a response from the server,
a callback is specified to handle the response once it's received. The
result of a request is only available inside the scope of a response
handler. Any execution contingent on the response or data received
from the server must be done within a handler.
Try to use a handler like this and a callback:
func getopt(callback:(array: [String]) -> void ){
func completion(request: NSURLRequest?, response:NSHTTPURLResponse?,result:Result<AnyObject>){
if let rdata = result.value{
let data = JSON(rdata)
print(data)
let myArray = [String]
let objects = data.array
for object in objects{
myArray.append(object)
}
callback(myArray)
}
}
let url = "http://www.xxxxxxxxxxxxx.com"
Alamofire.request(.GET,url),
encoding: .JSON).responseJSON(completionHandler: completion)
}
You pass the array to your callback So when you call getopt where you call it you can print the array. Some like this:
func something (){
getopt(callback)
}
func callback(array:[String]){
print array[0]
}