Use of delegates in NSOperation

后端 未结 4 786
滥情空心
滥情空心 2021-02-09 23:24

I am trying to make use of CLLocationManager in an NSOperation. As part of this I require the ability to startUpdatingLocation then wait u

4条回答
  •  庸人自扰
    2021-02-09 23:49

    I think you have two options.

    1. Create a separate thread, with its own run loop, for location services:

      #import "LocationOperation.h"
      #import 
      
      @interface LocationOperation () 
      
      @property (nonatomic, readwrite, getter = isFinished)  BOOL finished;
      @property (nonatomic, readwrite, getter = isExecuting) BOOL executing;
      
      @property (nonatomic, strong) CLLocationManager *locationManager;
      
      @end
      
      @implementation LocationOperation
      
      @synthesize finished  = _finished;
      @synthesize executing = _executing;
      
      - (id)init
      {
          self = [super init];
          if (self) {
              _finished = NO;
              _executing = NO;
          }
          return self;
      }
      
      - (void)start
      {
          if ([self isCancelled]) {
              self.finished = YES;
              return;
          }
      
          self.executing = YES;
      
          [self performSelector:@selector(main) onThread:[[self class] locationManagerThread] withObject:nil waitUntilDone:NO modes:[[NSSet setWithObject:NSRunLoopCommonModes] allObjects]];
      }
      
      - (void)main
      {
          [self startStandardUpdates];
      }
      
      - (void)dealloc
      {
          NSLog(@"%s", __FUNCTION__);
      }
      
      #pragma mark - NSOperation methods
      
      - (BOOL)isConcurrent
      {
          return YES;
      }
      
      - (void)setExecuting:(BOOL)executing
      {
          if (executing != _executing) {
              [self willChangeValueForKey:@"isExecuting"];
              _executing = executing;
              [self didChangeValueForKey:@"isExecuting"];
          }
      }
      
      - (void)setFinished:(BOOL)finished
      {
          if (finished != _finished) {
              [self willChangeValueForKey:@"isFinished"];
              _finished = finished;
              [self didChangeValueForKey:@"isFinished"];
          }
      }
      
      - (void)completeOperation
      {
          self.executing = NO;
          self.finished = YES;
      }
      
      - (void)cancel
      {
          [self stopStandardUpdates];
          [super cancel];
          [self completeOperation];
      }
      
      #pragma mark - Location Manager Thread
      
      + (void)locationManagerThreadEntryPoint:(id __unused)object
      {
          @autoreleasepool {
              [[NSThread currentThread] setName:@"location manager"];
      
              NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
              [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
              [runLoop run];
          }
      }
      
      + (NSThread *)locationManagerThread
      {
          static NSThread *_locationManagerThread = nil;
          static dispatch_once_t oncePredicate;
          dispatch_once(&oncePredicate, ^{
              _locationManagerThread = [[NSThread alloc] initWithTarget:self selector:@selector(locationManagerThreadEntryPoint:) object:nil];
              [_locationManagerThread start];
          });
      
          return _locationManagerThread;
      }
      
      #pragma mark - Location Services
      
      - (void)startStandardUpdates
      {
          if (nil == self.locationManager)
              self.locationManager = [[CLLocationManager alloc] init];
      
          self.locationManager.delegate = self;
          self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
          self.locationManager.distanceFilter = 500;
      
          [self.locationManager startUpdatingLocation];
      }
      
      - (void)stopStandardUpdates
      {
          [self.locationManager stopUpdatingLocation];
          self.locationManager = nil;
      }
      
      #pragma mark - CLLocationManagerDelegate
      
      - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
      {
          CLLocation* location = [locations lastObject];
      
          // do whatever you want with the location
      
          // now, turn off location services
      
          if (location.horizontalAccuracy < 50) {
              [self stopStandardUpdates];
              [self completeOperation];
          }
      }
      
      @end
      
    2. Alternatively, even though you're using an operation, you could just run location services on the main thread:

      #import "LocationOperation.h"
      #import 
      
      @interface LocationOperation () 
      
      @property (nonatomic, readwrite, getter = isFinished)  BOOL finished;
      @property (nonatomic, readwrite, getter = isExecuting) BOOL executing;
      
      @property (nonatomic, strong) CLLocationManager *locationManager;
      
      @end
      
      @implementation LocationOperation
      
      @synthesize finished  = _finished;
      @synthesize executing = _executing;
      
      - (id)init
      {
          self = [super init];
          if (self) {
              _finished = NO;
              _executing = NO;
          }
          return self;
      }
      
      - (void)start
      {
          if ([self isCancelled]) {
              self.finished = YES;
              return;
          }
      
          self.executing = YES;
      
          [self startStandardUpdates];
      }
      
      #pragma mark - NSOperation methods
      
      - (BOOL)isConcurrent
      {
          return YES;
      }
      
      - (void)setExecuting:(BOOL)executing
      {
          if (executing != _executing) {
              [self willChangeValueForKey:@"isExecuting"];
              _executing = executing;
              [self didChangeValueForKey:@"isExecuting"];
          }
      }
      
      - (void)setFinished:(BOOL)finished
      {
          if (finished != _finished) {
              [self willChangeValueForKey:@"isFinished"];
              _finished = finished;
              [self didChangeValueForKey:@"isFinished"];
          }
      }
      
      - (void)completeOperation
      {
          self.executing = NO;
          self.finished = YES;
      }
      
      - (void)cancel
      {
          [self stopStandardUpdates];
          [super cancel];
          [self completeOperation];
      }
      
      #pragma mark - Location Services
      
      - (void)startStandardUpdates
      {
          [[NSOperationQueue mainQueue] addOperationWithBlock:^{
              if (nil == self.locationManager)
                  self.locationManager = [[CLLocationManager alloc] init];
      
              self.locationManager.delegate = self;
              self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
              self.locationManager.distanceFilter = 500;
      
              [self.locationManager startUpdatingLocation];
          }];
      }
      
      - (void)stopStandardUpdates
      {
          [self.locationManager stopUpdatingLocation];
          self.locationManager = nil;
      }
      
      #pragma mark - CLLocationManagerDelegate
      
      - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
      {
          CLLocation* location = [locations lastObject];
      
          // do whatever you want with the location
      
          // now, turn off location services
      
          if (location.horizontalAccuracy < 50) {
              [self stopStandardUpdates];
              [self completeOperation];
          }
      }
      
      @end
      

    I think I'd be inclined to do the second approach (just making sure that I don't do anything too intensive in didUpdateLocations, or if I did, make sure to do it asynchronously), but both of these approaches appear to work.

    Another approach is to keep the run loop alive until the operation is finished:

    while (![self isFinished]) {
        [[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
    }
    

    But this doesn't appear to work in conjunction with CLLocationManager, as runUntilDate doesn't immediately return (it's almost as if CLLocationManager is attaching its own source to the runloop, which prevents it from exiting). I guess you could change the runUntilDate to something a little closer than distantFuture (e.g. [NSDate dateWithTimeIntervalSinceNow:1.0]). Still, I think it's just as easy to run this operation start location services on the main queue, like the second solution above.

    Having said that, I'm not sure why you would want to use location manager in an operation at all. It's already asynchronous, so I would just start the location manager from the main queue and call it a day.

提交回复
热议问题