I need help trying to figure out this crash.
I have a common crash (#1 crash, ~300 devices affected) that seems to occur when my app is checking CLLocation authorization status:
The 'start' method is as follows:
-(BOOL)start {
if ([self isDenied]) {
return NO;
} else {
_manager = [[CLLocationManager alloc] init];
_manager.delegate = self;
if ([_manager respondsToSelector:#selector(requestAlwaysAuthorization)] && [self isNotDetermined]) {
[_manager requestAlwaysAuthorization];
} else {
[self startUpdatingLocation];
}
return YES;
}
}
The 'isNotDetermined' method, from which the application seems to switch to a different thread and crash, is:
- (BOOL)isNotDetermined {
return CLLocationManager.authorizationStatus == kCLAuthorizationStatusNotDetermined;
}
Maybe also relevant, the isDenied method; according to the documentation, the CLLocationManager should not be created if the authorization status is Denied or Restricted:
- (BOOL)isDenied {
return CLLocationManager.authorizationStatus == kCLAuthorizationStatusDenied;
}
Could it be that the app crashes because I create a CLLocationManager while the authorizationStatus is Restricted? I'd expect the crash to occur in the CLLocationManager init method in that case.
I / we have managed to find the problem. The problem was twofold:
stopUpdatingLocation was not called when the application entered the background.
When the application entered the foreground, the CLLocationManager was re-initialized, without first stopping the old instance. Two bits of code:
// called in both didFinishLaunching and applicationWillEnterForeground
-(void) start {
_manager = [[CLLocationManager alloc] init];
// other setup code
}
// Called in applicationDidEnterBackground
-(void)stop {
// this variable was never false
if (_updateBackgroundLocations) {
[_manager stopUpdatingLocation];
}
}
What I did to fix it:
-(void) start {
if (_manager == nil) {
_manager = [[CLLocationManager alloc] init];
}
// other setup code
}
-(void)stop {
[_manager stopUpdatingLocation];
}
This fixed... several thousand crashes. They probably occurred when the application was already in the background, since we never did get complaints about frequently occurring crashes from our users.
Related
I'm building an app on iOS 10 using the Estimote SDK. I want to use the range mode to detect nearby beacons. The UUID for the region and beacons is setup correctly as verified in a small sample project.
The app I'm building right now is showing some strange behaviour: After starting the app, the beaconManager:didRangeBeacons:inRegion: method is not called even when right next to the beacon.
Disabling / enabling Bluetooth will cause the method to fire immediately. This is also true for pausing the app and resuming it using the debugger.
What is causing this behaviour? I'm requesting the permission at every launch and wait for the callback to start monitoring (as stated in the documentation). I already tried to setup more startRanging / stopRanging calls (desperate!) but no success.
Any ideas?
#implementation Model {}
- (instancetype)init {
self = [super init];
if (self) {
self.beaconManager = [ESTBeaconManager new];
self.beaconManager.delegate = self;
self.beaconRegion = [[CLBeaconRegion alloc]
initWithProximityUUID:[[NSUUID alloc]
initWithUUIDString:proximityUUID]
identifier:#"Playground"];
[self.beaconManager requestWhenInUseAuthorization];
self.beaconSignal = [self rac_signalForSelector:#selector(beaconManager:didRangeBeacons:inRegion:) fromProtocol:#protocol(ESTBeaconManagerDelegate)];
[[self.beaconSignal throttle:1]
subscribeNext:^(id x) {
NSLog(#"Did range fired");
}];
}
return self;
}
- (void)start {
[self.beaconManager startRangingBeaconsInRegion:self.beaconRegion];
}
- (void)beaconManager:(id)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
if(status == kCLAuthorizationStatusAuthorizedWhenInUse){
[self start];
}
}
The problem is the throttled subscription to RACSignal for the beaconManager:didRangeBeacons:inRegion:selector. Removing the throttle or the delegate method without a signal works fine.
The didChangeAuthorizationStatus is called kCLAuthorizationStatusDenied state in setting app > Privacy > Location Services > My app > Never in background mode.
But After changing 'Always', didChangeAuthorizationStatus is not called.
Also not called when 'Location Services' off.
I inserted NSLocationAlwaysUsageDescription at my Info.plist and set location update background mode.
My code,
- (id)init {
self = [super init];
if (self) {
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
_locationManager.pausesLocationUpdatesAutomatically = NO;
if ([_locationManager respondsToSelector:#selector(setAllowsBackgroundLocationUpdates:)]) {
NSLog(#"setAllowsBackgroundLocationUpdates");
_locationManager.allowsBackgroundLocationUpdates = YES;
}
[self requestAuthorization];
}
return self;
}
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
NSLog(#"CLAuthorizationStatus: %d", status);
}
Sorry my english.
I was having this exact problem and it turned out I was creating my CLLocationManager and calling
-requestWhenInUseAuthorization
on a background thread. I called those methods on the main thread and got the expected results (-locationManager:didChangedAithorizationStatus: was called after I tapped "Don't Allow" or "Allow")
To summarize Newtz comment....
Swift 3
var locationManager:CLLocationManager? = .none
...
// Set up your location manager as desired
DispatchQueue.main.async {
self.locationManager = CLLocationManager()
self.locationManager?.delegate = self
self.locationManager?.activityType = self.activityType
self.locationManager?.desiredAccuracy = self.desiredAccuracy
self.locationManager?.distanceFilter = self.distanceFilter
self.locationManager?.allowsBackgroundLocationUpdates = true
self.locationManager?.pausesLocationUpdatesAutomatically = false
}
...
// Authorize
DispatchQueue.main.async {
// If you're not in the background you like need requestWhenInUseAuthorization()
self.locationManager?.requestAlwaysAuthorization()
}
I am testing on an iPad 3rd gen on iOS 7.1 since I have no other iOS device for the moment.
The first time I run my app, it starts monitoring for several regions. The status bar and the Location Services settings page are showing the outlined location services icon (my app is the only one in the list that has the outlined icon). When I kill my app, the icon is still showing on both places since I do not stop monitoring the regions yet. Until then everything is fine.
My problem is when I run my app for a second time, I stop monitoring for all monitored regions, but the location services outlined icon does not disappear on the status bar and the Location Services settings page...
Here is my code called at the first run :
- (void) getLocationManagerInstance {
if (!self.locationManager) {
self.locationManager = [CLLocationManager new];
}
self.locationManager.delegate = self;
}
- (void) startLocationGathering {
if(self.shouldUpdateGPSLocations) {
[self.locationManager startMonitoringSignificantLocationChanges];
}
}
- (void) startMonitoringBeaconRegions {
if(self.rootRegion) {
[self.locationManager startMonitoringForRegion:self.rootRegion];
}
if (self.beaconRegions && self.beaconRegions.count < 20) {
[self.beaconRegions enumerateObjectsUsingBlock:^(CLBeaconRegion* region, NSUInteger idx, BOOL *stop) {
[self.locationManager startMonitoringForRegion:region];
}];
}
}
- (void) startMonitoringCircularRegions {
if (self.gpsRegions && self.gpsRegions.count) {
[self.gpsRegions enumerateObjectsUsingBlock:^(CLCircularRegion* region, NSUInteger idx, BOOL *stop) {
[self.locationManager startMonitoringForRegion:region];
}];
}
}
And my code called at the second run :
- (void) getLocationManagerInstance {
if (!self.locationManager) {
self.locationManager = [CLLocationManager new];
}
self.locationManager.delegate = self;
}
- (void) locationManagerCleanup {
[AWRUtils dlog:#"locationManagerCleanup"];
NSArray* monitoredRegions = [self.locationManager monitoredRegions].allObjects;
for (CLRegion* r in monitoredRegions) {
[self.locationManager stopMonitoringForRegion:r];
}
NSArray* rangedRegions = [self.locationManager rangedRegions].allObjects;
for (CLBeaconRegion* r in rangedRegions) {
[self.locationManager stopRangingBeaconsInRegion:r];
}
[self.locationManager stopUpdatingLocation];
[self.locationManager stopMonitoringSignificantLocationChanges];
}
If I uninstall my app, the outlined location services icon disappear. But why is the icon not disappearing when I stop monitoring the monitored regions?
EDIT: After more testing, I found that the instance of the CLLocationManager that I have on the second run has no monitored regions ([self.locationManager monitoredRegions] returns nil)...
EDIT 2: I also found that if, on the second run, I start monitoring all the same regions that started monitoring on the first run AND THEN I stop monitoring them, the outlined location services icon disappear. Is this a normal behavior? I read nothing about that in all my internet researches...
I'm having some problems when I try and run my "xcode" project and I get this "run time" error.
Assertion failure in -[CLLocationManager startRangingBeaconsInRegion:], /SourceCache/CoreLocationFramework/CoreLocation-1613.35/Framework/CoreLocation/CLLocationManager.m:991
2014-05-11 15:47:15.483 Ziew[1516:60b] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: region != nil'
I'm trying use the "Estimote SDK" to build a similar application using the proximity application code example. The original example works great and I didn't change anything when I added their code. Here some of my methods:
- (id)initWithBeacon:(ESTBeacon *)beacon
{
self = [super init];
if (self)
{
self.beacon = beacon;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[[UITabBar appearance] setSelectedImageTintColor:[UIColor colorWithRed:(141/255.0) green:(198/255.0) blue:(63/255.0) alpha:1]];
// Do any additional setup after loading the view.
//Beacon Manager setup
self.beaconManager = [[ESTBeaconManager alloc] init];
self.beaconManager.delegate = self;
self.beaconRegion = [[ESTBeaconRegion alloc] initWithProximityUUID:self.beacon.proximityUUID
major:[self.beacon.major unsignedIntValue]
minor:[self.beacon.minor unsignedIntValue]
identifier:#"RegionIdentifier"];
[self.beaconManager startRangingBeaconsInRegion:self.beaconRegion];
}
#pragma mark - ESTBeaconManager delegate
- (void)beaconManager:(ESTBeaconManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(ESTBeaconRegion *)region
{
if (beacons.count > 0)
{
ESTBeacon *firstBeacon = [beacons firstObject];
[self textForProximity:firstBeacon.proximity];
}
NSLog(#"No beacons within region");
}
#pragma mark -
- (void)textForProximity:(CLProximity)proximity
{
switch (proximity) {
case CLProximityFar:
NSLog(#"Far");
break;
case CLProximityNear:
NSLog(#"Near");
break;
case CLProximityImmediate:
NSLog(#"Immediate");
break;
default:
NSLog(#"Unknown");
break;
}
}
- (void)viewDidDisappear:(BOOL)animated
{
[self.beaconManager stopRangingBeaconsInRegion:self.beaconRegion];
[super viewDidDisappear:animated];
}
What am I missing?
SOLUTION
Thank you to everyone who responded. Using the Estimote SDK you can assign a constant UUID with the beacon corresponding major and minor id to get the startRangingBeaconsInRegion method called.
self.beaconRegion = [[ESTBeaconRegion alloc] initWithProximityUUID:ESTIMOTE_PROXIMITY_UUID
major:your_major_id
minor:your_minor_id
identifier:#"RegionIdentifier"];
From what I can tell your beacon region is nil by the time you tell it to start ranging beacons. Since you assign to your property an initialized instance of a region, maybe your property declaration is listed as weak instead of strong?
You have probably copied initWithBeacon constructor from their demos and this constructor is not being called when view controller is being instantiated (like from xib or storyboard).
Because self.beacon is nil, constructed ESTRegion object is allocated with nil params which causes crash.
You need to:
range for beacons around you first
or
hardcode beacon identifiers in your app (or fetch it from somewhere)
I am using the Core Location framework to locate the device and once locationManager:didUpdateToLocation:fromLocation: method is called with a location, I stop tracking the user.
However the first time I launch the app (from a fresh install). When I message startUpdatingLocation of my Location Manager, the user gets the alert to accept or refuse location service.
When I accept the tracking doesn't begin, it's only when I go away and come back to this view controller when startUpdatingLocation is again called that notifications start coming in.
I am implementing locationManager:didChangeAuthorizationStatus: thinking that this would get messaged when the user accepts (or refuses) location services, but it doesn't.
Can anyone point me in the right direction for updating location as soon as the location services message has been dismissed ?
Thanks.
UPDATE WITH CODE SAMPLE
I've got a singleton class which encapsulates my logic, the idea is when the user location is requested, a check on the CLLocation's timestamp is performed and if it's too old, start tracking is messaged, which lazy loads my CLLocationManager iVar,
-(void)startTracking{
if(!self.locationManager)
_locationManager = [[CLLocationManager alloc] init];
if([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied){
[self invalidateUserLocation];
}else{
self.locationManager.delegate = self;
[self.locationManager startUpdatingLocation];
}
}
New location received:
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
_userLocation = [newLocation retain];
NSDictionary * info = [NSDictionary dictionaryWithObject:self.userLocation
forKey:#"userLocation"];
[[NSNotificationCenter defaultCenter] postNotificationName:kUserLocationFound
object:self
userInfo:info];
[self stopTracking];
}
Stop tracking:
-(void)stopTracking{
if(!self.locationManager)
return;
[self.locationManager stopUpdatingLocation];
self.locationManager.delegate = nil;
}
When I have a view controller which needs the users location I call userLocation on my singleton object like so. If it's recent, I return the CLLocation, otherwise I return nil and start again. Notice I stop tracking when I receive the first update. But the first time this runs and I get the alert view, nothing is tracked at all.
- (CLLocation*)userLocation
{
if(_userLocation.coordinate.latitude == 0 && _userLocation.coordinate.longitude == 0){
[self startTracking];
return nil;
}else{
NSDate* timeNow = [NSDate date];
NSTimeInterval interval = [timeNow timeIntervalSinceDate:_userLocation.timestamp];
if(interval >10)
[self startTracking];
return _userLocation;
}
}
Did you try calling – locationManager:didChangeAuthorizationStatus: from CLLocationManagerDelegate?
I'm going to guess that you call startTracking when the view controller loads. This is circumvented by the alert which ask for if it's okay. At that point, the start locating message won't be called again so by calling didChangeAuthorizationStatus, you can call your startTracking method.
Something like:
-(void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
if (status == kCLAuthorizationStatusDenied) {
//location denied, handle accordingly
}
else if (status == kCLAuthorizationStatusAuthorized) {
//hooray! begin startTracking
}
}
If that's not the case, let me know.