在NSOperation中使用委托

我想在NSOperation使用CLLocationManager 。 作为这个的一部分,我需要startUpdatingLocation的能力,然后等待,直到收到一个CLLocation完成操作。

目前我做了以下工作,但是这个委托方法似乎从来没有被调用过。 请有人build议是什么问题?

 - (void)main { @autoreleasepool { if (self.isCancelled) return; // Record the fact we have not found the location yet shouldKeepLooking = YES; // Setup the location manager NSLog(@"Setting up location manager."); CLLocationManager *locationManager = [[CLLocationManager alloc] init]; locationManager.delegate = self; locationManager.desiredAccuracy = kCLLocationAccuracyBest; [locationManager startUpdatingLocation]; while (shouldKeepLooking) { if (self.isCancelled) return; // Do some other logic... } } } - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { // None of this ever seems to be called (despite updating the location) latestLocation = [locations lastObject]; [manager stopUpdatingLocation]; shouldKeepLooking = NO; } 

它将调用与main相同的操作队列中的委托方法。NSOperation队列默认是串行的。 你的while循环只是永远的旋转(因为操作永远不会被取消),并且对你的委托方法的调用被放在队列后面,永远不能运行。

完全摆脱while循环,让操作完成。 然后当委托方法被调用时,如果取消则返回结果放弃。

回到runloop讨论,这是我通常解决在我的基础NSOperation实现:

 // create connection and keep the current runloop running until // the operation has finished. this allows this instance of the operation // to act as the connections delegate _connection = [[NSURLConnection alloc] initWithRequest:[self request] delegate:self]; while(!self.isFinished) { [[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]]; } 

我closuresisFinished ,我通过setter更新isCancelledisFinished 。 以isCancelled setter为例:

 - (void)setIsCancelled:(BOOL)isCancelled { _isCancelled = isCancelled; if (_isCancelled == YES) { self.isFinished = YES; } } 

这就是说,我第二个问题是为什么这是必要的。 如果在find某个位置之前不需要启动某些function,为什么不直接在主线程上启动位置pipe理器,等待相应的代理callback,然后启动后台操作?

更新:更新解决scheme

虽然原来的答案一般,我已经完全实现了一个解决scheme,它需要稍微改变你如何pipe理运行循环。 也就是说,所有的代码都可以在GitHub上find – https://github.com/nathanhjones/CLBackgroundOperation 。 这里是对方法的详细解释。

文艺青年最爱的

更改

 [[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]]; 

 [[NSRunLoop currentRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate distantFuture]]; 

细节

在您的操作界面中定义以下三个属性。 我们将指出这些操作是并发的,因此我们将手动pipe理它们的状态。 在GitHub的解决scheme中,这些是NJBaseOperation一部分。

 @property(nonatomic,assign,readonly) BOOL isExecuting; @property(nonatomic,assign,readonly) BOOL isFinished; @property(nonatomic,assign,readonly) BOOL isCancelled; 

在你的操作实现中,你需要使这些readwrite如下所示:

 @interface NJBaseOperation () @property(nonatomic,assign,readwrite) BOOL isExecuting; @property(nonatomic,assign,readwrite) BOOL isFinished; @property(nonatomic,assign,readwrite) BOOL isCancelled; @end 

接下来,您将要综合上面定义的三个属性,以便您可以覆盖setter并使用它们来pipe理您的操作状态。 以下是我通常使用的方法,但有时会根据我的需要,将一些额外的语句添加到setIsFinished:方法中。

 - (void)setIsExecuting:(BOOL)isExecuting { _isExecuting = isExecuting; if (_isExecuting == YES) { self.isFinished = NO; } } - (void)setIsFinished:(BOOL)isFinished { _isFinished = isFinished; if (_isFinished == YES) { self.isExecuting = NO; } } - (void)setIsCancelled:(BOOL)isCancelled { _isCancelled = isCancelled; if (_isCancelled == YES) { self.isFinished = YES; } } 

最后,为了不必手动发送KVO通知,我们将实施以下方法。 这是有效的,因为我们的属性被命名为isExecutingisFinishedisCancelled

 + (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key { return YES; } 

现在,操作基础已经到了淘汰位置的时候了。 你会想重载main并在其中启动你的位置pipe理器,并指示当前的运行循环继续运行,直到你告诉它,否则。 这可以确保您的线程正在接收位置委托callback。 这是我的实现:

 - (void)main { if (_locationManager == nil) { _locationManager = [[CLLocationManager alloc] init]; _locationManager.delegate = self; _locationManager.desiredAccuracy = kCLLocationAccuracyBest; [_locationManager startUpdatingLocation]; } while(!self.isFinished) { [[NSRunLoop currentRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate distantFuture]]; } } 

您应该收到一个委托callback,您可以根据位置进行一些工作,然后完成操作。 这是我的实现数到10,000,然后清理。

 - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { NSLog(@"** Did Update Location: %@", [locations lastObject]); [_locationManager stopUpdatingLocation]; // do something here that takes some length of time to complete for (int i=0; i<10000; i++) { if ((i % 10) == 0) { NSLog(@"Loop %i", i); } } self.isFinished = YES; } 

GitHub上的源代码包含一个dealloc实现,该实现简单地logging它被调用,并观察对我的NSOperationQueueoperationCount的更改,并logging计数 – 指示何时回落到0。 如果你有问题,请告诉我。

我想你有两个select。

  1. 使用自己的运行循环为位置服务创build一个单独的线程:

     #import "LocationOperation.h" #import <CoreLocation/CoreLocation.h> @interface LocationOperation () <CLLocationManagerDelegate> @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. 或者,即使您正在使用操作,也可以在主线程上运行位置服务:

     #import "LocationOperation.h" #import <CoreLocation/CoreLocation.h> @interface LocationOperation () <CLLocationManagerDelegate> @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 

我想我会倾向于采取第二种方法(只要确保我在didUpdateLocations没有做太多密集的didUpdateLocations ,或者如果我这样做的话,确保它是asynchronous的),但是这两种方法似乎都有效。

另一种方法是保持运行循环直到操作完成:

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

但是,这似乎不能与CLLocationManager一起使用,因为runUntilDate不会立即返回(就好像CLLocationManager将自己的源代码附加到runloop,从而阻止它退出一样)。 我想你可以改变runUntilDatedistantFuture更近一些(例如[NSDate dateWithTimeIntervalSinceNow:1.0] )。 不过,我认为在主队列上运行这个操作启动位置服务就像上面的第二个解决scheme一样简单。

话虽如此,我不确定你为什么要在一个操作中使用位置pipe理器。 它已经是asynchronous的了,所以我只需要从主队列中启动位置pipe理器,然后每天调用它。

带有UIWebViewDelegate方法的UIWebView方法在NSOperation中callback

一个服务器,我想从服务器抓取一个URL,根据各种浏览器的JavaScript执行更改值。 所以我打了一个虚拟的UIWebView到一个NSOperation,并用它来抓取UIWebViewDelegate方法中我想要的值。

 @interface WBSWebViewOperation () <UIWebViewDelegate> @property (assign, nonatomic) BOOL stopRunLoop; @property (assign, nonatomic, getter = isExecuting) BOOL executing; @property (assign, nonatomic, getter = isFinished) BOOL finished; @property (copy, nonatomic, readwrite) NSURL *videoURL; @property (strong, nonatomic) UIWebView *webView; @end @implementation WBSWebViewOperation - (id)init { self = [super init]; if (self) { _finished = NO; _executing = NO; } return self; } - (id)initWithURL:(NSURL *)episodeURL { self = [self init]; if (self != nil) { _episodeURL = episodeURL; } return self; } - (void)start { if (![self isCancelled]) { self.executing = YES; [self performSelector:@selector(main) onThread:[NSThread mainThread] withObject:nil waitUntilDone:NO modes:[[NSSet setWithObject:NSRunLoopCommonModes] allObjects]]; } else { self.finished = YES; } } - (void)main { if (self.episodeURL != nil) { NSURLRequest *request = [NSURLRequest requestWithURL:self.episodeURL]; UIWebView *webView = [[UIWebView alloc] init]; webView.delegate = self; [webView loadRequest:request]; self.webView = webView; } } #pragma mark - NSOperation methods - (BOOL)isConcurrent { return YES; } - (void)setExecuting:(BOOL)executing { [self willChangeValueForKey:@"isExecuting"]; _executing = executing; [self didChangeValueForKey:@"isExecuting"]; } - (void)setFinished:(BOOL)finished { [self willChangeValueForKey:@"isFinished"]; _finished = finished; [self didChangeValueForKey:@"isFinished"]; } - (void)completeOperation { self.executing = NO; self.finished = YES; } - (void)cancel { [self.webView stopLoading]; [super cancel]; [self completeOperation]; } #pragma mark - UIWebViewDelegate methods - (void)webViewDidFinishLoad:(UIWebView *)webView { NSString *episodeVideoURLString = [webView stringByEvaluatingJavaScriptFromString:@"document.getElementById('playerelement').getAttribute('data-media')"]; NSURL *episodeVideoURL = [NSURL URLWithString:episodeVideoURLString]; self.videoURL = episodeVideoURL; if ([self.delegate respondsToSelector:@selector(webViewOperationDidFinish:)]) { [self.delegate webViewOperationDidFinish:self]; } [self completeOperation]; } @end