Search code examples
iosobjective-cunity-game-engine

How to collect compass data, in iOS, when a Unity Application is in background?


I have few knowledge of Objective-C and Swift but after spending several hours to write a native plugin to collect compass data using a background thread in iOS, below there is the plugin so far

// MyPlugin.m
 
extern "C" {
#import <CoreLocation/CoreLocation.h>
#import "MyPlugin.h"
 
@implementation MyPlugin
 
NSThread *_magneticHeadThread;
BOOL _shouldStop;
NSMutableArray *readings;
 
- (void)startBackgroundThread {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        // Put your code here that should run in the background when the app enters background.
        NSLog(@"Background thread started!");
        readings = [NSMutableArray array];
        _magneticHeadThread = [[NSThread alloc] initWithTarget:self selector:@selector(retrieveMagneticHeadPosition) object:nil];
        [_magneticHeadThread start];
    });
}
 
- (void)applicationDidEnterBackground:(NSNotification *)notification {
    _shouldStop = NO;
    [self startBackgroundThread];
}
 
- (void)applicationWillEnterForeground:(NSNotification *)notification {
    _shouldStop = YES;
    [_magneticHeadThread cancel];
    NSLog(@"Magnetic head position: %lu", [readings count]);
}
 
+ (instancetype)sharedInstance {
    static MyPlugin *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[MyPlugin alloc] init];
    });
    return sharedInstance;
}
 
- (void)retrieveMagneticHeadPosition {
    CLLocationManager *locationManager = [[CLLocationManager alloc] init];
   
    locationManager.headingFilter = kCLHeadingFilterNone;
    [locationManager startUpdatingHeading];
    int c = 0;
    while (!_shouldStop) {
       
        NSLog(@"Magnetic head position: %i", c);
        c++;
        [readings addObject:@(locationManager.heading.magneticHeading)];
        [NSThread sleepForTimeInterval:0.1];
    }
   
    [locationManager stopUpdatingHeading];
}
 
@end
 
 
void UnityMyPlugin_StartBackgroundThread() {
    [[NSNotificationCenter defaultCenter] addObserver:[MyPlugin sharedInstance] selector:@selector(applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
   
    [[NSNotificationCenter defaultCenter] addObserver:[MyPlugin sharedInstance] selector:@selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
}
}

It is called in a MonoBehaviour in a Start function:

void Start()
    {
        Application.runInBackground = true;
#if UNITY_IPHONE && !UNITY_EDITOR
        UnityMyPlugin_StartBackgroundThread();
#endif
    }

I've read that dispatch_async expects to finish in a fixed time, but i haven't find any clue for indefinite thread nor information about services to request informations such as magneticHeading, can you point me out where to check or who to ask for a solution? Am I looking for anything possible? Did I make any bad choice in this code? Thanx

This solution, which compile and work for ~ 2s before being canceled by the SO but I'm looking for a thread which collect data for a longer period or a service to ask a range of timestamp and obtain infos.


Solution

  • Your approach here is wrong. You don't constantly poll CLLocationManager on a thread. You start CLLocationManager one time, and let it run for as long as you want the information. The app will receive periodic updates. There is no need for a thread of any kind, and definitely not a polling loop.

    For obtaining permissions, and other required setup, see Configuring your app to use location services. For more details on reading location information in the background, see Handling location updates in the background. Your app does not continually poll for information in the background. The OS will deliver events when they are available, potentially launching your app if needed.

    While no threads or queues are required for your current problem, it's worth noting that DISPATCH_QUEUE_PRIORITY_BACKGROUND is a very low-priority queue that often does not run at all when the app is in the background. (I'd never really thought about how counter-intuitive that name is.) Even in cases where this code should run while the app is in the background, you wouldn't want to use that priority.

    As a rule, if you find yourself using sleepForTimeInterval:, you're probably doing something wrong. It's sometimes the correct tool, but it's extremely rare, particularly if you're a beginner.