I have a LocationManager in a viewController from where I want to listen for significant changes in user's location. Then, when locationManager:didUpdateToLocation:fromLocation: triggered, I perform some data processing by calling a custom object that is a private property of the same viewController and is always the same object:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
if (dataProcessor == nil) {
dataProcessor = [[DataProcessor alloc] initWithDefaultValues];
}
[dataProcessor processData:someData];
}
The method called by the dataProcessor, in turn, later makes calls to another self methods, and the whole processing performed by this dataProcessor may have not finished before locationManager:didUpdateToLocation:fromLocation: is reached again and then another [dataProcessor processData:someData] call is made.
My question is, what is supposed to be the behavior in this situation? Being the same object called, is the second call queued till the first one has finished? Or could the second call be executed between the call of the different methods called from the first processData call? I don't know how to see and debug this in Xcode. I need to ensure that data processing is performed and completed in the order in which new locations are notified by the Location Manager. I am not currently making use of any thread I've created, nor GCD queues, nor semaphores, because I am not sure what is really happening there and I don't know if I need any of those features.
Thanks a lot
processData method will be executed as a serial queue, because it's executed on a main thread.
http://developer.apple.com/library/ios/#documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html
Related
Below are some snippets of my code
BOOL checkingForLocation;
.
.
.
- (IBAction)LocationButtonTapped:(id)sender {
[locationManager startUpdatingLocation];
checkingForLocation = YES;
}
.
.
.
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations {
[locationManager stopUpdatingLocation];
// locationManager = nil;
if (checkingForLocation) {
checkingForLocation = NO;
NSLog(#"here");
// fetch data from server using location and present view controllers
}
}
didUpdateLocations is called multiple times because locationManager is continually updating the location per startUpdatingLocation. My app has a bug where the code within if(checkingForLocation) runs multiple times and renders the view multiple times causing undesired results.
1) Why is it that stopUpdatingLocation doesn't stop further calls to didUpdateLocations?
2) Furthermore, if the checkingForLocation has already been set to NO why does the subsequent calls to didUpdateLocations still treat checkingForLocation as YES. Is there a race condition there?
Through searching other SO questions, I found that setting locationManager = nil will stop the extra calls to didUpdateLocations, which it does fix the issue in my case, but no real explanation. It seems that stopUpdatingLocation should take care of that. Any insight is much appreciated.
Thank you,
didUpdateLocations: can be called frequently and at any time because of its async nature and optimisations :
Because it can take several seconds to return an initial location, the
location manager typically delivers the previously cached location
data immediately and then delivers more up-to-date location data as it
becomes available. Therefore it is always a good idea to check the
timestamp of any location object before taking any actions. If both
location services are enabled simultaneously, they deliver events
using the same set of delegate methods.
Actually, if you uncomment locationManager = nil; it should help, but if you want to use your checkingForLocation property, you should make the assignment synchronised. #synchronized(self) is the easiest way in this case.
I want execute a function all time while my ios App is running.
What is the class where I need write this function, in the delegate?
I'm confused because if I declared this in viewContorller and change to other viewController this break. Or there is a function like
func locationManager(manager:CLLocationManager, didUpdateLocations locations:[AnyObject]) {
that this is running all time?
Thanks!
If you are referring specifically to didUpdateLocations, this is a method that you don't call directly, but rather the OS calls to deliver location updates to you any time it receives them. While it's typically recommended that your location code is handled by some singleton somewhere to consolidate/encapsulate all the location logic, if you create a CLLocationManager and tell it to startUpdatingLocations, that method will be called constantly** by the OS without you having to deal with timers, loops etc.
** When the app is backgrounded the location updates will stop, and when the app comes back to the foreground the location updates will resume without you needing to restart them. These can come as often as once per second, but once again relies on the OS determining the location of the devices and delivering those updates to you.
If you're referring to anything else that is a different answer, but I suspect you're referring directly to location updates.
I suggest to put the function in another file, and use an NSThread
- (void)createThread
{
[NSThread detachNewThreadSelector:#selector(startBackgroundJob) toTarget:self withObject:nil];
}
- (void)startBackgroundJob
{
[self performSelectorOnMainThread:#selector(monitorApp) withObject:nil waitUntilDone:NO];
}
- (void)monitorApp
{
}
On iOS, in my application delegate I start region monitoring and as soon as I enter in a beacon region I start the ranging logic, using locationManager:didRangeBeacons:inRegion. According to the Apple documentation, this method should be called only when the the region comes within the range or out of the range or when the range changes.
My problem is that I get a call to this method every second as long as I am inside the region. How to decrease the number of calls to this method while still ranging?
locationManager:didRangeBeacons:inRegion is called once per second, no matter what. Each time it's called, the beacons parameter will contain an array of all beacons that the app can currently see, ordered by proximity. There's no way to limit the frequency at which this method is called, short of stopping ranging.
When monitoring regions (instead of ranging), your app will have didEnterRegion: and didExitRegion called, along with didDetermineState:. See this answer for a little more detail.
According to the Docs:
"The location manager calls this method whenever a beacon comes within range or goes out of range. The location manager also calls this method when the range of the beacon changes; for example, when the beacon gets closer."
Whats probably happening is the range is changing slightly which is causing the behaviour you describe.
Why is this a problem
EDIT:
IN the background you will get notified of entering regions via the app delegate method:
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region{}
You can use this to determine the state:
if(state == CLRegionStateInside)
{
//Inside a region:
}
else if(state == CLRegionStateOutside)
{
//Outside a region
}
else {
//Something else
}
You can use this to gather a limited amount of information or prompt the user to load the application via a local notification. When your app resumes you can then gather more information via the locationManager.
I have location services enabled for updating locations when app in background, and I also listen for location updates when app in foreground, displaying a map. What should be the best way to design this scenario in iOS? I've thought about some options:
1) Having an instance of a class with a locationManager member that is its delegate itself. Then, in the body of the didUpdateToLocation delegate method, something like:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
if (!background) {
// Perform some processing and notify the view controller which displays the map
// by means of Notification Center
}
else {
appDelegate.bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:appDelegate.bgTask];
appDelegate.bgTask = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Perform some data processing
// Close background task
if (appDelegate.bgTask != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:appDelegate.bgTask];
appDelegate.bgTask = UIBackgroundTaskInvalid;
}
});
}
}
(Note: I'm not sure if having location services enabled as background service, it is needed to perform the processing of locations as if it were a finite background task...). The instance of such class could be a member of AppDelegate, and start listening for locations when entering background or calling the instance from the viewModel displaying the map. So, AppDelegate would have a reference to the instance managing the locationManager, and the map viewController would have a reference to AppDelegate, or communicate also by means of Notification Center.
2) Having the locationManager member directly in AppDelegate, and being the delegate itself.
Would it be better to encapsulate de locations listening and management in a different class, or handle it directly in AppDelegate? Having into account that I have to be able to listen for locations and perform some tasks both in foreground and in background.
Thanks in advance
I think your best strategy is to have a singleton instance of a class that is a locationManager delegate. This instance would be responsible to filter whatever comes back from locationManager and ultimately assigns the location to one of its properties, let's call it myLoc.
This solves your centralized logic issues where the location needs post-processing after being acquired.
Second, in order to get the correct UIViewControllers and other class instances to do what they need when the location is updated, I suggest you use Key-Value Observing (KVO) on myLoc. I assume you know how that works. The reason I suggest KVO is that you need to trigger changes in multiple areas of the code based on the location being changed, and KVO is made for that kind of pattern.
I would like to add more to #Rikkles answer by saying that you can create a BaseViewController, which is a subclass of UIViewController and has an instance of your singleton locationManager class too.
This approach allows us to move all the redundant code like, checking reachability, showing activity indicator,getting user location, etc to only one place and will be available to all other classes implementing it.
How would I implement a RACSignal that would stop publishing when there are no subscribers to it and auto start when there are subscribers?
Here is a scenario:
Let us say I have a currentLocationSignal in the AppDelegate.
My LocationViewController would subscribe to the currentLocationSignal when view loads and unsubscribe (dispose) when view unloads. Since it takes few seconds to get the current location, I would like to always subscribe to the currentLocationSignal when the app opens (and auto unsubscribe after few seconds), so by the time I arrive to LocationViewController I would get an accurate location.
So there can be more then one subscribers to the signal. When the first subscriber listens, it needs to start calling startUpdatingLocation and when there are no subscribers it needs to call stopUpdatingLocation.
Good question! Normally, you'd use RACMulticastConnection for use cases like this, but, because you want the signal to be able to reactivate later, a connection isn't suitable on its own.
The simplest answer is probably to mimic how a connection works, but with the specific behaviors you want. Basically, we'll keep track of how many subscribers there are at any given time, and start/stop updating the location based on that number.
Let's start by adding a locationSubject property. The subject needs to be a RACReplaySubject, because we always want new subscribers to get the most recently sent location immediately. Implementing updates with that subject is easy enough:
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
[self.locationSubject sendNext:locations.lastObject];
}
Then, we want to implement the signal that tracks and increments/decrements the subscriber count. This works by using a numberOfLocationSubscribers integer property:
- (RACSignal *)currentLocationSignal {
return [RACSignal createSignal:^(id<RACSubscriber> subscriber) {
#synchronized (self) {
if (self.numberOfLocationSubscribers == 0) {
[self.locationManager startUpdatingLocation];
}
++self.numberOfLocationSubscribers;
}
[self.locationSubject subscribe:subscriber];
return [RACDisposable disposableWithBlock:^{
#synchronized (self) {
--self.numberOfLocationSubscribers;
if (self.numberOfLocationSubscribers == 0) {
[self.locationManager stopUpdatingLocation];
}
}
}];
}];
}
In the above code, the +createSignal: block is invoked every time a new subscriber is added to the returned signal. When that happens:
We check to see if the number of subscribers is currently zero. If so, the just-added subscriber is the first one, so we need to enable (or re-enable) location updates.
We hook the subscriber directly up to our locationSubject, so the values from the latter are automatically fed into the former.
Then, at some future time, when the subscription is disposed of, we decrement the count and stop location updates if appropriate.
Now, all that's left is subscribing to the currentLocationSignal on startup, and automatically unsubscribing after a few seconds:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Use a capacity of 1 because we only ever care about the latest
// location.
self.locationSubject = [RACReplaySubject replaySubjectWithCapacity:1];
[[self.currentLocationSignal
takeUntil:[RACSignal interval:3]]
subscribeCompleted:^{
// We don't actually need to do anything here, but we need
// a subscription to keep the location updating going for the
// time specified.
}];
return YES;
}
This subscribes to self.currentLocationSignal immediately, and then automatically disposes of that subscription when the +interval: signal sends its first value.
Interestingly, -[RACMulticastConnection autoconnect] used to behave like -currentLocationSignal above, but that behavior was changed because it makes side effects wildly unpredictable. This use case should be safe, but there are other times (like when making a network request or running a shell command) when automatic reconnection would be horrible.