Asynchronous function not updating variable - ios

var endLat: Double = 0.0
var endLong: Double = 0.0
func forwardGeocodingStarting(address: String) {
CLGeocoder().geocodeAddressString(address, completionHandler: { (placemarks, error) in
if error != nil {
print(error)
return
}
let placemark = placemarks?[0]
let location = placemark?.location
let coordinate = location?.coordinate
dispatch_async(dispatch_get_main_queue()) {
startLat = (coordinate?.latitude)!
startLong = (coordinate?.longitude)!
print("\(startLat) is lat and \(startLong) is long")
}
})
}
Hello, here is a geocoding function that I created, that simply takes an address and returns the address's coordinates. My problem is, that at the end of the code section, when I do print(endLat) it prints out 0, however when I do it in the dispatch_async in the function, it comes out to the proper latitude.
I realize this is an asynchronous function, which is why I tried to use the dispatch_async to update the variable instantly.
My question is, how can I have these variables update instantly? Thanks in advance.

You've got a little confused with what you mean by "Asynchronous function not updating variable". forwardGeocodingStarting(_:) is NOT an Async function. geocodeAddressString is an async function designed by the API to allow you to use the following values you get after the operation is done within that function only. Hence you keep getting 0 as the it doesn't wait till the operation is done completely to display the value. dispatch_async(dispatch_get_main_queue) is used only to update UI on the Main thread. You've to use a completion handler in your function like so:
typealias theDouble = (Double, Double) -> ()
func forwardGeocodingStarting(address: String, completion: theDouble){
//your code
let aVar: Double!
let bVar: Double!
let placemark = placemarks?[0]
let location = placemark?.location
let coordinate = location?.coordinate
aVar = (coordinate?.latitude)!
bVar = (coordinate?.longitude)!
completion(aVar, bVar)
}
Now when you call your function, update the variables startLat and startLong like so:
forwardGeocodingStarting(<your string address>) { firstVar, secondVar in
startLat = firstVar
startLong = secondVar
print("\(startLat) is lat and \(startLong) is long") //You will get correct values
}

You cannot have these variables update instantaneously, as you are calling an asynchronous function. Thats an obvious contradiction :-)
That you dispatch back to the main queue is fine, but that doesn't change the asynchronous behaviour. In fact your are even issuing yet another dispatch_async ... (meaning the body of that code will get run at some later point in time)
You could wait for the results to be available, e.g. using a dispatch group, but quite likely that is not what you want here.
I assume you want to use the values in some view controller? In this case you probably fill in some instance variables as the results arrive and update the UI state once both values are available.

Related

Alamofire could not update class property data

Hi i am trying to use alamofire to download json weather data. Here is my code, the working version:
class WeatherModel {
private var _date: String?
private var _location: String?
private var _weatherType: String?
private var _temperature: Double?
func getWeatherInfoFromAPI(completed: #escaping ()-> ()) {
let url = URL(string: WEATHER_URL)!
Alamofire.request(url).responseJSON(completionHandler: { response in
// Test updating data
self._temperature = 25
self._weatherType = "Clear"
self._location = "Vietnam"
completed()
})
}
}
-> This way, i am able to update the property of the class.
Failing to update class property version of getWeatherInfoFromAPI func:
func getWeatherInfoFromAPI(completed: #escaping ()-> ()) {
let url = URL(string: WEATHER_URL)!
Alamofire.request(url).responseJSON{ response in
// Test updating data
self._temperature = 25
self._weatherType = "Clear"
self._location = "Vietnam"
}
completed()
}
So, i dont know what is the difference between them. Please help me to clarify between 2 ways here.
Alamofire.request(url).responseJSON(completionHandler: { response in })
and
Alamofire.request(url).responseJSON{ response in }
What is the reason that my code does not work? Since i see the Alamofire docs also use like the second way! I am thinking about thread difference between them
Also, how do i know what thread the code is running in responseJSON?
Thanks, i appreciate your time and help!
Those two ways are functionally identical, the second one just uses Swift's trailing closure syntax.
What do you do in completed()? Because in first example, you are calling it upon completion of network call, and in second case you are calling it immediately after you start the network call - the call is not completed yet. You should call if in Alamofire callback, like in first example. In second example, if you're inspecting those properties inside completed, then it's no wonder they're not updated yet.

Error acessing list of Realm's objects: Realm accessed from incorrect thread

I have a list of points of interest. This points were loaded from a Realm database. Each point should present its distance to the user's position.
Each time I get a new location, I calculate the distance to all points. To avoid a frozen screen, I was doing the math in a background thread, after i display the list in a table in the main thread.
func updatedLocation(currentLocation: CLLocation) {
let qualityOfServiceClass = QOS_CLASS_BACKGROUND
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
for point in self.points{
let stringDistance = self.distanceToPoint(currentLocation, destination: point.coordinate)
point.stringDistance = stringDistance
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView?.reloadData()
})
})
}
However I get this error:
libc++abi.dylib: terminating with uncaught exception of type realm::IncorrectThreadException: Realm accessed from incorrect thread.
I know i am getting this error because I'm accessing the realm objects in a background thread, however, they are already loaded into an array and I never make a new query to the database.
In addition, the var i'm updating his not saved into the database.
Any idea how to solve this? I wanted to avoid doing the math in the main thread.
thanks in advance
I assume you wrap Realm Results objects into Array like the following:
let results = realm.objects(Point)
self.points = Array(results)
However, that is not enough. Because each element in the array is still tied with Realm, that cannot be access another thread.
A recommended way is re-create Realm and re-fetch the Results each threads.
dispatch_async(backgroundQueue, {
let realm = try! Realm()
let points = realm.objects(...)
try! realm.write {
for point in points{
let stringDistance = self.distanceToPoint(currentLocation, destination: point.coordinate)
point.stringDistance = stringDistance
}
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
...
})
})
Realm objects have live-update feature. When committed changed to Realm objects on sub-thread, those changes reflect to the objects in other thread immediately. So you do not need to re-fetch the query in the main thread. What you should do is just reload the table view.
If you'd like to wrap array and pass it to other thread directly, you should wrap all elements of resutls as follows:
let results = realm.objects(Point)
self.points = results.map { (point) -> Point in
return Point(value: point)
}

Data from HealthKit is initially missing, then comes in when I try again - how to fix?

I have an iOS app that I'm developing in Swift, and as part of the app it gathers the step count for the current day.
The first time I run the app, the count is "0", but if I click a button in the interface to re-run this function that queries HK, then the correct number appears.
I am guessing this is because HK needs some time to gather the data, or something, but I'm not sure how to fix it. Maybe HK can fire an event when the data is ready, and then I can update the UI?
Here's the routine that gathers the data from HK. This function executes immediately when the app start (and it shows "0 steps today"), and then as I describe above, I can tap a button to execute the function again (and then I get the right number).
func queryStepsSum() {
// prepare HK for the data
let endDate = NSDate() // right now
let startDate = NSCalendar.currentCalendar().dateBySettingHour(0, minute: 0, second: 0, ofDate: endDate, options: NSCalendarOptions())
let predicate = HKQuery.predicateForSamplesWithStartDate(startDate, endDate: endDate, options: .None)
let sumOption = HKStatisticsOptions.CumulativeSum
let statisticsSumQuery = HKStatisticsQuery( quantityType: self.stepsCount!, quantitySamplePredicate: predicate,
options: sumOption)
{ [unowned self] (query, result, error) in
if let sumQuantity = result?.sumQuantity() {
self.numberOfSteps = Int(sumQuantity.doubleValueForUnit(HKUnit.countUnit()))
}
}
// run the HK query
self.healthStore?.executeQuery(statisticsSumQuery)
// update the UI with the result
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.stepsLabel.text = "\(self.numberOfSteps) steps today";
});
}
Your code doesn't wait for the query to finish before displaying the result (the query happens asynchronously). You should update your UI from the query's result handler, like this:
let statisticsSumQuery = HKStatisticsQuery( quantityType: self.stepsCount!, quantitySamplePredicate: predicate,
options: sumOption)
{ [unowned self] (query, result, error) in
dispatch_async(dispatch_get_main_queue()) {
if let sumQuantity = result?.sumQuantity() {
self.numberOfSteps = Int(sumQuantity.doubleValueForUnit(HKUnit.countUnit()))
self.stepsLabel.text = "\(self.numberOfSteps) steps today";
}
}
}
Also note that I've included a dispatch back to the main thread since the query results handler runs on a background thread and it's not safe to manipulate UI in that context.
Are you calling your function in viewDidLoad? You might try calling it in viewDidAppear instead.
If that doesn't work maybe there is some sort of delegate method that can update you when the data is ready like you said. I've never worked with HealthKit but I'll check to see if I can find anything.
EDIT:
This might set the UI component off the background thread.
Try:
var numberOfSteps: Int! {
didSet {
self.stepsLabel.text = "\(self.numberOfSteps) steps today"
}
}
That should get rid of the error that you're updating the UI on the background thread. Also make sure to remove your call in the closure that I told you to put.

Convert asynchronously large no of GPS data to city/country

Background: I have a list of contacts, which are retrieved on an asynchronous queue from a cloud based database. Once done, I dispatch back to the main queue and show these contacts in a TableView.
Besides names and other details, each contact object has GPS coordinate properties (latitude and longitude). I want to use these GPS coordinates to retrieve the name of the city and country of each contact and update the TableView showing that information in the local language of the device the user has.
Problem: The problem I am trying to overcome is that I have a few hundred contacts. Initially, I used concurrent queues to get the city/country strings. But I paused the app in XCode and realised the disaster of 300+ threads created. So I changed the code running each lookup of the city/country on the same serial queue.
The issue now is that only for about 50 of the contacts the data is updated. I do not know why and not sure even if the queue is serial. Debugging shows there are still 100+ “serial queues” created. I expected one at the time. What am I doing wrong? Thanks
The code for the class, in which I have my TableView is as follows:
dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.rawValue), 0)) {
// get or update the contactsList
activeUser.contactsList.getAllContacts()
// once we have the contacts we go back to the main queue
dispatch_async(dispatch_get_main_queue()) {
// and refresh tableview to show the contacts
self.tableView.reloadData()
// now we refresh the locations of the contacts
for index in 0...activeUser.contactsList.listOfContacts.count
{
// set local variables for lat and long
let lat = activeUser.contactsList.listOfContacts[index].latitude
let long = activeUser.contactsList.listOfContacts[index].longitude
// call location service method with completion handler
MyLocationServices().updateLocationToLocalLanguage(lat, longitude: long, completionHandler:
{ (city, country) -> () in
// update contact's details
activeUser.contactsList.listOfContacts[index].city = city
activeUser.contactsList.listOfContacts[index].country = country
// refresh each time the table to show updated contact data
self.tableView.reloadData()
})
}
}
The method within the MyLocationServices class looks as follows:
func updateLocationToLocalLanguageDispatched(latitude: String, longitude: String, completionHandler: (city: String, country: String) -> ())
{
let serialQ = dispatch_queue_create("AddressUpdateQ", DISPATCH_QUEUE_SERIAL)
dispatch_async(serialQ)
{
var cityString = "NA"
var countryString = "NA"
let group = dispatch_group_create()
dispatch_group_enter(group)
let location = CLLocation(latitude: Double(latitude)!, longitude: Double(longitude)!)
self.geocoder.reverseGeocodeLocation(location)
{
(placemarks, error) -> Void in
if let placemarks = (placemarks as [CLPlacemark]!) where placemarks.count > 0
{
let placemark = placemarks[0]
if ((placemark.addressDictionary!["City"]) as? String != nil) { cityString = ((placemark.addressDictionary!["City"]) as? String)! }
if ((placemark.addressDictionary!["Country"]) as? String != nil) { countryString = ((placemark.addressDictionary!["Country"]) as? String)! }
}
dispatch_group_leave(group)
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
dispatch_async(dispatch_get_main_queue())
{
completionHandler(city: cityString, country: countryString)
}
}
}
I have solved the issue and documenting hoping it is helpful in case others face similar problems.
(1) Serial Queue: I have realised serial queue initiation should not happen within the method, but enclose the for-loop. Thus the following code should be removed from the method and put around the for-loop. But this does not solve the fact that I only get about 50 results and nothing for the remaining GPS data of the contacts.
let serialQ = dispatch_queue_create("AddressUpdateQ", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQ)
{ [for index....] }
(2) Location: The second issue is related to Apple's Geocoder, which happens online. Officially, the number of requests are limited. The documentation states that
one "should not send more than one geocoding request per minute".
Applications should be conscious of how they use geocoding. Geocoding
requests are rate-limited for each app, so making too many requests in
a short period of time may cause some of the requests to fail.
Checking the error code I get, it is Error Domain=kCLErrorDomain Code=2. It seems the service is denied and I cannot make further requests.
(3) Solutions: There are two solutions in my view. One is to update 30 or so users, wait some time, and then dispatch after that another queue to get again some data. The second solution is to live with having all data in English, make a string that each user saves in the cloud. So for other users retrieving contacts, it's just a matter of fetching a string from the database rather than checking GPS data "on the fly".

Realm Threading Confusion

So I'm working on setting up a background queue that does all realm writes on its own thread. I've run into some strange issues I can't figure out.
Issue #1
I'm not sure if this is related (see post: Xcode debug issues with realm) but I do have an apparent mismatch with my lldbg output as to whether a certain field:
messages element
My DataTypes
OTTOSession
class OTTOSession : Object {
dynamic var messages : MessageList?
dynamic var recordingStart : Double = NSDate().timeIntervalSince1970
func addLocationMessage(msg : dmParsedMessage) -> LocationMessage {
let dmsg : dmLocationMessage = msg as! dmLocationMessage
let locMsg = LocationMessage(locMsg: dmsg)
self.messages!.locationMessages.append(locMsg)
return locMsg;
}
}
MessageList
public class MessageList : Object {
dynamic var date : NSDate = NSDate();
dynamic var test : String = "HI";
let locationMessages = List<LocationMessage>()
let ahrsMessages = List<AHRSMessage>()
// let statusMessages = List<StatusMessageRLM>()
let logMessages = List<LogMessage>()
}
Realm Interactions
In my code I create my new OTTOSession in a code block on my realmQueue
internal var realmQueue = dispatch_queue_create("DataRecorder.realmQueue",
DISPATCH_QUEUE_SERIAL)
All realm calls are done on this realmQueue thread
dispatch_async(realmQueue) {
self.session = OTTOSession()
}
I've also tried different variants such as:
dispatch_async(realmQueue) {
self.session = OTTOSession()
// Directly making a message list
self.session!.messages = MessageList()
//Making a separate message list var
self.messages = MessageList()
self.session!.messages = self.messages
}
The reason I've played around with the MessageList is that I cant tell from the debugger whether the .messages variable is set or not
Recording
Once I signal to my processes I want to start recording I then actually make the write calls into Realm (which I'm not 100% sure i'm doing correctly)
dispatch_async(realmQueue){
// Update some of the data
self.session!.recordingStart = NSDate().timeIntervalSince1970
// Then start writing the objects
try! Realm().write {
// I've tried different variants of:
let session = self.session!
try! Realm().add(self.session!)
// Or
try! Realm().add(self.session!)
// or
let session = self.session!
session.messages = MessageList()
session.messages!.ahrsMessages
try! Realm().add(self.session!)
try! self.session!.messages = Realm().create(MessageList)
try! Realm().add(self.session!.messages!)
print ("Done")
}
}
Basically I've tried various combinations of trying to get the objects into realm.
Question: When adding an object with a one-to-one relationship do I have to add both objects to Realm or will just adding the parent object cause the related object to also be added to realm
Adding Data
Where things start to go awry is when I start adding data to my objects.
Inside my OTTOSession Object I have the following function:
func addLocationMessage(msg : dmParsedMessage) -> LocationMessage {
let dmsg : dmLocationMessage = msg as! dmLocationMessage
let locMsg = LocationMessage(locMsg: dmsg)
// THIS LINE IS CAUSING A 'REALM ACCESSED FROM INCORRECT THREAD ERROR
self.messages!.locationMessages.append(locMsg)
return locMsg;
}
I'm getting my access error on this line:
self.messages!.locationMessages.append(locMsg)
Now the function call itself is wrapped in the following block:
dispatch_async(realmQueue) {
try! Realm().write {
self.session?.addLocationMessage(msg)
}
}
So as far as I can tell by looking at the debugger - and by looking at the code - everything should be running inside the right thread.
My queue is SERIAL so things should be happening one after another. The only thing I can't figure out is when I break at this point the debugger does show that messages is nil but I cant trust that because:
Question
So my question is two fold
1) Is my code for adding an object into the RealmDB correct. i.e. do I need to make two separate Realm().add calls for both the OTTOSession and the MessageList or can I get away with a single call
2) Is there anything that pops out to explain why I'm getting a thread violation here - should doing all my realm writing calls on a single thread be enough ?
1) No, you don't need to make two separate calls to Realm.add(). When you add an object to a Realm all related objects are persisted as well.
2) Your thread violation very likely originates from the fact that dispatch queues make no guarantee over the thread on which they are executed on (beside the main queue). So that means your Realm queue is executed on different threads. You will need to make sure to retrieve your session object from a Realm opened on this thread. You might want to use primary keys for that purpose and share those between queues / threads.

Resources