I have a recursive function with an async request. I want to save in an array if the requests were successful, but i don't know how.
Concrete it is a function to upload files and if the function get a folder than the files inside this folder should also be uploaded.
I thought about implement this with a completionHandler, something about this:
func uploadFiles(pathToFile: NSURL) ->[Bool]{
var suc: [Bool] = [Bool]()
self.uploadFileRec(pathToFile, suc: &suc){
(result: [Bool]) in
println(result)
}
return suc
}
func uploadFilesRec(pathToFile: NSURL, inout suc: [Bool], completionHandler: (result: [Bool]) -> Void){
var isDir: ObjCBool = ObjCBool(true)
var manager: NSFileManager = NSFileManager.defaultManager()
manager.fileExistsAtPath(pathToFile.path!, isDirectory: &isDir)
if(isDir){
var error: NSError? = nil
let contents = manager.contentsOfDirectoryAtPath(pathToFile.path!, error: &error) as! [String]
for fileName in contents {
if(fileName != ".DS_Store"){
var pathString = pathToFile.path! + "/" + fileName
var updatePathtoFile = NSURL(fileURLWithPath: pathString)
self.uploadFilesRec(updatePathtoFile!, suc: &suc, completionHandler: completionHandler)
completionHandler(result: suc)
}
}
}
else{
asyncFileUpload(...){
...
suc.append(/*successful or not*/)
}
}
}
But the problem is that println get call not only one time but as many as uploadFileRec get called inside. So if i would call an another function instead of println, the function would be also called many times.
So i think the idea with completionHandler was wrong. How else can i realize this?
Ok i answer my own question.
The idea with complitionHandler was indeed false. Like I'm saying in the question, the complitionHandler is called as many times as the recursive function is called. If you want to collect the responses or like in my app collect if the upload of some files was successful you must use dispatch group. The main idea is to add all request in this group and wait until all are done.
In practice it means create group:
let group = dispatch_group_create()
Enter the group before you call the recursive function and then each time the function call itself:
dispatch_group_enter(self.group)
Leave the group when the request is done and as many time as you enter the group:
dispatch_group_leave(self.group)
And wait until all work is done:
dispatch_group_notify(group, dispatch_get_main_queue()) {
//Do work here
}
var isDir: ObjCBool = ObjCBool(true) so, you treat all files as dirs by default, and when manager.fileExistsAtPath fails you get deep recursion because isDirectory flag stays TRUE:
If path doesn’t exist, this value is undefined upon return
from Apple Doc...
var pathString = pathToFile.path! + "/" + fileName - not sure your're getting correct path in the end. So, you getting recursion. Check your pathString
manager.fileExistsAtPath result is ignored, so, you're doing blind upload...
Fix that things and keep going...
Related
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.
I am working with the GoogleMaps API, this returns 20 nearby locations and up to 60 locations can be returned. The locations are returned with a nextPageToken which allows you to GET the next page of results (20 per page). I am trying to loop through the API to enable myself to get all the locations available but am having difficulty:
func getAllNearbyLocations(url: URL) {
I am using Alamofire to return the API request (I have also tried using URLSessions)
First I put together a function which returns the json dictionary in the completion block
// This function returns the JSON from a specific URL
func getJsonFromURL(url: URL, completionHandler: #escaping (NSDictionary) -> ()) {
Alamofire.request(url).responseJSON { response in
let json = response.result.value as! NSDictionary
completionHandler(json)
}
}
Next we have a getNearByLocation function which we initialise with a url. As you can see we return the results, add them to an array, check if we have the max number of results (60) or don't have a nextPageToken any more. If either of these are false we create the new URL and fire the function we are currently in again. The loop finishes when we return all the new locations.
func getAllNearbyLocations(url: URL) {
self.getJsonFromURL(url: url) { (dictionary) in
let newLocations: NSArray = dictionary.value(forKey: "results") as! NSArray
self.locations.addObjects(from: newLocations as! [Any])
if self.locations.count >= 60 {
self.sendNewLocations(locations: self.locations)
}
else {
// We want to now update the URL we are using and search again
if let newPageToken = dictionary["next_page_token"] {
let newURL = self.rootSearchURL + "&pagetoken=" + (newPageToken as! String)
let url = URL(string: newURL)
// We want to get our current URL and remove the last characters from it
self.getAllNearbyLocations(url: url!)
}
else {
// If we have no more pages then we return what we have
self.sendNewLocations(locations: self.locations)
}
}
}
}
The strange thing is when I test the code with breakpoints it returns as expected. It loops through the function, adds all the new locations and returns. When I run it in realtime the returned dictionary doesn't return properly (doesn't contain the locations or next page token) and so my function only returns the first 20 locations.
I have used API requests before but never so close in succession. I feel that it is a catch 22 as I can't know the new pageToken until I have called the request and as soon as I have returned the request I want to call the request with that token immediately.
As per the Documentation,
There is a short delay between when a next_page_token is issued, and when it will become valid. Requesting the next page before it is available will return an INVALID_REQUEST response. Retrying the request with the same next_page_token will return the next page of results.
Try to check what you are getting when you are retrieving data with new_page_token
Try to call like this :
let newURL = self.rootSearchURL + "&pagetoken=" + (newPageToken as! String)
let url = URL(string: newURL)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) {
// We want to get our current URL and remove the last characters from it
self.getAllNearbyLocations(url: url!)
}
This might be a basic question but I am unable to figure this out. My function always returns a null value and I am not sure why.
My main code ( in a different view controller) calls this function with a "string" to search. But at the end of the function, I can never return the results. If i print the search results where I have the final print statement, then I do see the results. But right after that function is complete, the return value is nil. How would I get the return value from this function ? Thanks in advance,. I am using Swift btw.
func fetchSearchResults (_ searchString: String) -> Array<Any> {
self.globalPost = [Posts]()
self.postKeysArray = []
var searchIndex:AlgoliaSearch.Index
let query = Query()
searchIndex = self.client.index(withName: "Comments")
// you can ignore all of this below until the last couple lines...
query.attributesToRetrieve = ["userComment","postId"]
query.attributesToHighlight = ["postId"]
query.query = searchString
query.optionalWords = [searchString]
let curSearchId = searchId
searchIndex.search(query, completionHandler: { (content, error) in
if (curSearchId <= self.displayedSearchId) || (error != nil) {
return // Newest query already displayed or error
}
self.displayedSearchId = curSearchId
self.loadedPage = 0 // Reset loaded page
guard let hits = content!["hits"] as? [[String: AnyObject]] else { return }
var tmp = [String]()
for hit in hits {
// print ("Comment is \(commentRecord(json:hit).postId)")
tmp.append(commentRecord(json:hit).postId!)
}
self.postKeysArray = tmp
self.getDatafromFirebase(searchString)
// so my search code returns some values here and i see them in the post keysArray.
print ("Search Results -post keys is \(self.postKeysArray)") \\ returns correct values here in the Array
})
self.searchId += 1
// But here i get a NULL value.
return self.postKeysArray \\ returns empty array
}
As Alexander noted, you are printing the search results inside the completion handler, which has not executed yet when fetchSearchResults returns. There are a couple different ways that you could solve this. One would be to refactor fetchSearchResults to take a closure:
func fetchSearchResults (_ searchString: String, completionHandler completion: (Array<Any>) -> ()) {
// ...
searchIndex.search(query, completionHandler: { (content, error) in
// ...
self.postKeysArray = tmp
self.getDatafromFirebase(searchString)
// so my search code returns some values here and i see them in the post keysArray.
print ("Search Results -post keys is \(self.postKeysArray)") \\ returns correct values here in the Array
completion(self.postKeysArray)
})
}
Then, in your 'main code', wherever you were calling:
let results = searchController.fetchSearchResults(searchString)
doSomething(with: results)
You would now have:
searchController.fetchSearchResults(searchString, { results in
doSomething(results)
})
This line could also be written:
searchController.fetchSearchResults(searchString, doSomething)
If you really can't have fetchSearchResults be asynchronous, then you could use DispatchSemaphore to wait until the completion handler has finished:
func fetchSearchResults (_ searchString: String) -> Array<Any> {
// ...
var semaphore = DispatchSemaphore(0)
searchIndex.search(query, completionHandler: { (content, error) in
// ...
self.postKeysArray = tmp
self.getDatafromFirebase(searchString)
// so my search code returns some values here and i see them in the post keysArray.
print ("Search Results -post keys is \(self.postKeysArray)") \\ returns correct values here in the Array
semaphore.signal()
})
semaphore.wait()
return self.postKeysArray
}
Note that unless you know what you're doing, using semaphores is not a good idea. If you don't know what threads various callbacks might be called on, it can lead to a deadlock. The above example will not work if the searchIndex.search completion handler is called on the same DispatchQueue as fetchSearchResults. If you don't understand what I'm talking about, you probably shouldn't do it. It will almost certainly be better to use asynchronous callbacks.
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]
}
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.