Im using MapKit's CLGeocoder().geocodeAddressString() to get the coordinates and county information for a list of addresses. Everything works great as long as the # of requests is under 50. but anything over 50 and im hitting API's limit. Since CLGeocoder calls are asynchronous, i can't easily throttle/control the flow of the calls (calling one address at a time, for instance). How would I do this correctly in the "asynchronous world"? (DISCLAIMER: I'm new to the world of GCD and asynchronous flow control, so I think I might require a more detailed response)
Here's the relevant code:
method of Class Property that calls CLGeocoder on the Property's Adress:
func initializeCoordinates() {
let addressForCoords = self.address.getAddress()
CLGeocoder().geocodeAddressString(addressForCoords, completionHandler: { (placemarks, error) -> Void in
if error != nil {
print(error!)
return
}
if placemarks!.count > 0 {
let placemark = placemarks?[0]
let location = placemark?.location
self.coordinates = location?.coordinate
if let subAdminArea = placemark?.subAdministrativeArea {
self.address.county = subAdminArea
}
}
})
}
and then in the section in the ImportVC that imports all the property's addresses from a textBox (and makes the call to initializeCoordinates method on each Property:
for line in importText {
let newAddress = Address()
let newHouse = Property()
// parse the tab delimited address for each line of input
let address = line.components(separatedBy: "\t")
if address.count == 4 {
newAddress.street = address[0]
newAddress.city = address[1]
newAddress.state = trimState(state: address[2])
newAddress.zip = address[3]
newHouse.address = newAddress
newHouse.initializeCoordinates()
houses.append(newHouse)
}
}
I faced a similar problem recently. Replace your for-loop with a recursive function that calls itself at the end. However the trick is to call its self with a 0.2 second delay. I use 0.4 to be on the safe side. This will increase the waiting time for the user, although we have no choice due to the API limit.
Related
I'm trying to retrieve a specific child of my Firebase database using swiftUI. To do that I use the simple expression
func addListeners() {
let database = Database.database(url: "https://someUrl")
let ref = database.reference(withPath: "users")
let currentUserId = "u3Ebr6M3BAbP7PBSYYJ7q9kEe1l2"
let drivingTowardsRef = database.reference(withPath: "users/\(currentUserId)/drivingTowardsUsers")
print("Loading data from \(drivingTowardsRef)")
//THIS RIGHT HERE IS CAUSING THE PROBLEM
ref.observe(.childAdded) { snapshot in
print("Got TOP LEVEL data for user \(snapshot.key): \(String(describing: snapshot.value))")
}
//---------------------------------------
drivingTowardsRef.observe(.childAdded) { snapshot in
ref.child(snapshot.key).getData { (error, userSnapshot) in
if let error = error {
print(error)
} else {
print("Got arriving user data \(snapshot.key): \(String(describing: userSnapshot.value))")
}
}
}
}
The function will just return the entire database data
EDIT: The function returns the data from the first observer ref top level in this case users/ which in my case has two elements: niixi6iORjNn8gWq6tKvSi3Bxfc2, u3Ebr6M3BAbP7PBSYYJ7q9kEe1l2
Got arriving user data niixi6iORjNn8gWq6tKvSi3Bxfc2: Optional({
niixi6iORjNn8gWq6tKvSi3Bxfc2 = {
aproxTime = 0;
distance = 0;
latitude = "37.33070704";
longitude = "-122.03039943";
parkingMode = searching;
userId = niixi6iORjNn8gWq6tKvSi3Bxfc2;
username = testeroNumero;
};
u3Ebr6M3BAbP7PBSYYJ7q9kEe1l2 = {
aproxTime = 0;
distance = 0;
drivingTowardsUsers = {
niixi6iORjNn8gWq6tKvSi3Bxfc2 = {
approxTime = 0;
distance = "560.1447571016249";
};
};
latitude = "37.32984184";
longitude = "-122.02018095";
parkingMode = offering;
userId = u3Ebr6M3BAbP7PBSYYJ7q9kEe1l2;
username = cleoBadu;
};
The key for the child path I pass him seems to be correct but it's still returning the entire top level collection instead of the single item...
EDIT: The problem seems to be on the first observer which messes up the .getData() of the ref.child(snapshot.key). Is that even possible?
Just commenting out that ref.observe(.childAdded) will automatically make the second ref.child(snapshot.key) behave totally normally
What am I missing?
I could get the entire database as a single mega dictionary and then get the child I want from there but it doesn't seem really conventional, especially when google's library offers the possibility to not do that.
EDIT: I added a printing statement that prints the url of the database ref. If I then type in the url on my browser, it redirects me on the FRT database and landing me on the correct object. So the url it's generating is correct and works perfectly fine.
Still the object returned by the getData() is the entire db
SN: I removed all codable structs as that is not the problem, so the question is more focused on the actual problem
EDIT: Created a simple view as that. On a clean project it works on my project it doesn't. I guess it's some sort of configuration but's it's hard to look into it.
PROBLEM: Whatever child(string) I pass him it returns the entire top level data either way (replacing so snapshot.key). For example: I pass the key "something" -> all users are returned, I pass the key "" all users are returned
I just tried to reproduce the problem with (mostly) your code and data, but am not getting the same behavior.
I put the equivalent data into a database of mine at: https://stackoverflow.firebaseio.com/68956236.json?print=pretty
And used this code in Xcode 1.2 with Firebase SDK version 8.6.1:
let ref: DatabaseReference = Database.database().reference().child("68956236")
let currentUserId: String = "u3Ebr6M3BAbP7PBSYYJ7q9kEe1l2"
let drivingTowardsRef: DatabaseReference! = ref.child("\(currentUserId)/drivingTowardsUsers");
print("Loading data from \(drivingTowardsRef)")
drivingTowardsRef.observe(.childAdded) { snapshot in
ref.child(snapshot.key).getData { (error, userSnapshot) in
if let error = error {
print(error)
} else {
do {
//let parkingUser = try userSnapshot.data(as: ParkingUser.self)
print("Got data for user \(snapshot.key): \(String(describing: userSnapshot.value))")
} catch {
print("There has been an error while decoding the user location data with uid \(snapshot.key), the object to be decoded was \(userSnapshot). The decode failed with error: \(error)")
}
}
}
}
The output I get is:
Loading data from Optional(https://stackoverflow.firebaseio.com/68956236/u3Ebr6M3BAbP7PBSYYJ7q9kEe1l2/drivingTowardsUsers)
2021-08-27 10:39:09.578043-0700 Firebase10[36407:3458780] [] nw_protocol_get_quic_image_block_invoke dlopen libquic failed
Got data for user niixi6iORjNn8gWq6tKvSi3Bxfc2: Optional({
aproxTime = 0;
distance = 0;
latitude = "37.32798355";
longitude = "-122.01982712";
parkingMode = searching;
userId = niixi6iORjNn8gWq6tKvSi3Bxfc2;
username = testeroNumero;
})
As far as I can see this behavior is correct, but different from what you get. I hope knowing that I don't see the same behavior, and what versions I use, may be helpful to you.
This is not an issue with Firebase but rather client-side handling of the data returned, You’re expecting a Double within your Codable struct but supplying a String in the other end— Can you try:
public struct ParkingUser: Codable {
var latitude: String
var longitude: String
}
I'm having a pathfinder class in a SpriteKit game that a I want to use to process every path request in the game. So I have my class stored in my SKScene and I access it from different parts of the game always from the main thread. The pathfinder uses a GKGridGraph of a pretty good size (288 x 224). The class holds an array of requests processed one after another at each update() call from the main scene. Here is the code :
class PathFinder {
var isLookingForPath = false
var groundGraph : GKGridGraph<MyNode>?
var queued : [PathFinderRequest] = []
var thread: DispatchQoS.QoSClass = .userInitiated
func generate(minPoint: CGPoint) {
// generate the groundGraph grid
}
func update() {
// called every frame
if !self.isLookingForPath {
findPath()
}
}
func findPath(from start: TuplePosition, to end: TuplePosition, on layer: PathFinderLayer, callBack: PathFinderCallback) {
// Generating request
let id = String.randomString(length: 5)
let request = PathFinderRequest(id: id, start: start, end: end, layer: layer, callback: callBack)
// Append the request object at the end of the array
queued.append(request)
}
func findPath() {
self.isLookingForPath = true
guard let request = queued.first else {
isLookingForPath = false
return
}
let layer = request.layer
let callback = request.callback
let start = request.start
let end = request.end
let id = request.id
var graph = self.groundGraph
queued.removeFirst()
let findItem = DispatchWorkItem {
if let g = graph, let sn = g.node(atGridPosition: start.toVec()), let en = g.node(atGridPosition: end.toVec()) {
if let path = g.findPath(from: sn, to: en) as? [GKGridGraphNode], path.count > 0 {
// Here we have the path found
// it worked !
}
}
// Once the findPath() method execution is over,
// we reset the "flag" so we can call it once again from
// the update() method
self.isLookingForPath = false
}
// Execute the findPath() method in the chosen thread
// asynchronously
DispatchQueue.global(qos: thread).async(execute: findItem)
}
func drawPath(_ path: [GKGridGraphNode]) {
// draw the path on the scene
}
}
Well the code works quite good as it is. If I send random path request within (x+-10, y+-10) length it will return them to each object holding the callback in the request object pretty quickly, but suddenly one request is randomly taking a huge amount of time (approximatively 20s compared to 0.001s) and despite everything I tried I wasn't able to find out what happens. It's never on the same path, never the same caller, never after a certain amount of time... here is a video of the issue : https://www.youtube.com/watch?v=-IYlLOQgJrQ
It sure happens more quickly when there is too much entities requesting but I can't figure why I'm sure it has to deal with the DispacthQueue async calls that I use to prevent the game from freezing.
With delay on every call, the error appear later but is still here :
DispatchQueue.global(qos: thread).asyncAfter(deadline: .now() + 0.1, execute: findItem)
When I look for what is taking so much time to process it is a sub method of the GKGridGraph class :
So I really don't know how to figure this out, I tried everything I could think of but it always happens whatever the delay, the number of entities, the different threads, etc...
Thank you for your precious help !
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.
Sorry, newbie here and Ive read extensively about completion handlers, dispatch queues and groups but I just can't get my head around this.
My app loads an array of Google Place IDs and then wants to query Google to get full details on each place. The problem is, due to async processing the Google Lookup Place returns immediately and the callback happens much further down the line so whats the "proper way" to know when the last bit of data has come in for my inquiries because the function ends almost immedately ?
Code is attached. Thanks in advance.
func testFunc() {
let googlePlaceIDs = ["ChIJ5fTXDP8MK4cRjIKzek6L6NM", "ChIJ9Wd6mGYGK4cRiWd0_bkohHg", "ChIJaeXT08ASK4cRkCGpGgzYpu8", "ChIJkRkS4BapK4cRXCT8-SJxNDI", "ChIJ3wDV_2zX5IkRtd0hg2i1LhE", "ChIJb4wUsI5w44kRnERe7ywQaJA"]
let placesClient = GMSPlacesClient()
for placeID in googlePlaceIDs {
placesClient.lookUpPlaceID(placeID, callback: { (place, error) in
if let error = error {
print("lookup place id query error: \(error.localizedDescription)")
return
}
guard let place = place else {
print("No place details for \(placeID)")
return
}
print("Place Name = \(place.name)")
})
}
print("Done")
}
I am testing ReactiveCocoa in a playground. I would like to test for the validity of a string before performing a network request.
I am not sure how to combine the textfield signal and the validation signal tho. The part of the code that is marked [??] => what is the right way to do that filter?
var textField = UITextField()
textField.text = "http://localhost:3000/users.json"
let searchStringsProducer = textField.rac_textSignal().toSignalProducer()
let searchStringValidProducer = searchStringsSignal.toSignalProducer().map{
text in
(text as! String).containsString("http")
}
// this produces a ReactiveCocoa.SignalProducer<(Optional<AnyObject>, Bool), NSError>.
let searchStringCombined = combineLatest(searchStringsProducer, searchStringValidProducer)
// turns the simple search results into a signal
let searchResults = searchStringCombined
// [??] i would like to ONLY execute on the rest of the actions if it is valid
.map{ // what do i do here before passing on to the network API }
.flatMap(FlattenStrategy.Latest) {
latestStr -> SignalProducer<[AnyObject], NSError> in
return requestJSON(latestStr, parameters: nil)
.flatMapError { error in
print("Network error occurred: \(error)")
return SignalProducer.empty
}
}.observeOn(uis)
let searchStringValidProducer = searchStringsSignal.toSignalProducer().map { text in
(text as! String).containsString("http")
}
let searchStringCombined = combineLatest(searchStringsProducer, searchStringValidProducer)
This pattern is troubling, because when searchStringsProducer sends a value, searchStringCombined will send two values -- one for the new string, and one for the new boolean. It would be neater to define this signal like this:
let searchStringCombined = searchStringsSignal.toSignalProducer().map { text in
(text, (text as! String).containsString("http"))
}
Which gives you the same result.
But you don't need to define searchStringCombined at all, unless you're using it elsewhere. You should be able to get by with a simple filter:
searchStringsSignal.toSignalProducer().filter({ text in
(text as! String).containsString("http")
}).map({
/* whatever goes here */
}).flatMap(FlattenStrategy.Latest) {
/* perform network request */
}
filter is kind of like map, in that it takes a function and returns a signal. But it only lets some values through, allowing you to only make network requests based on the valid inputs.