5

我正在尝试CLLocationManagerNSOperation. 作为其中的一部分,我需要能够startUpdatingLocation等到收到 CLLocation 后再完成操作。

目前我已经完成了以下操作,但是似乎从未调用过委托方法。请问有人可以建议问题是什么吗?

- (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;
}
4

4 回答 4

5

回到 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]];
}

我关闭isFinished,我通过设置器保持更新isCancelledisFinished。下面以isCancelledsetter 为例:

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

也就是说,我支持一些关于为什么这是必要的问题。如果您在找到位置之前不需要启动某些东西,为什么不在主线程上启动您的位置管理器,等待适当的委托回调,然后启动后台操作呢?

更新:更新的解决方案

虽然最初的答案通常是正确的,但我已经完全实施了一个解决方案,它确实需要对您管理运行循环的方式进行轻微更改。也就是说,所有代码都可以在 GitHub 上找到 - https://github.com/nathanhjones/CLBackgroundOperation。这是该方法的详细说明。

Tl;博士

改变

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

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

细节

在您的操作界面中定义以下三个属性。我们将表明这些操作是并发的,因此我们将手动管理它们的状态。在 GitHub 上的解决方案中,这些是NJBaseOperation.

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

在您的操作实现中,您需要像这样使这些读写:

@interface NJBaseOperation ()

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

@end

接下来,您需要综合上面定义的三个属性,以便您可以覆盖设置器并使用它们来管理您的操作状态。这是我通常使用的,但有时会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 通知,我们将实现以下方法。这是有效的,因为我们的属性被命名为isExecuting,isFinishedisCancelled

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

现在运营基础已经处理好,是时候淘汰定位的东西了。您需要覆盖main并在其中启动您的位置管理器并指示当前运行循环继续运行,直到您另有说明。这确保您的线程可以接收位置委托回调。这是我的实现:

- (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]];
    }
}

您应该会收到一个委托回调,此时您可以根据位置进行一些工作,然后完成操作。这是我的实现,计数到 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实现,它只是记录它正在被调用,还观察operationCount我的变化NSOperationQueue并记录计数 - 以指示它何时回落到 0。希望有帮助。如果您有任何问题,请告诉我。

于 2013-08-25T15:05:46.813 回答
4

我认为你有两个选择。

  1. 为位置服务创建一个具有自己的运行循环的单独线程:

    #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.

另一种方法是让运行循环保持活动状态,直到操作完成:

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

但这似乎不能与 结合使用CLLocationManager,因为runUntilDate它不会立即返回(几乎就像CLLocationManager是将自己的源附加到运行循环,这会阻止它退出)。我想您可以将其更改为runUntilDatedistantFuture(例如[NSDate dateWithTimeIntervalSinceNow:1.0])更接近的东西。尽管如此,我认为在主队列上运行这个操作启动定位服务同样容易,就像上面的第二个解决方案一样。

话虽如此,我不确定您为什么要在操作中使用位置管理器。它已经是异步的,所以我会从主队列中启动位置管理器并称之为一天。

于 2013-08-25T17:34:38.327 回答
1

NSOperation 中带有 UIWebViewDelegate 方法回调的 UIWebView

一个服务器 我想从一个服务器中获取一个 URL,该 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
于 2014-01-01T11:34:07.797 回答
0

它会在 main 运行所在的同一操作队列中调用委托方法。NSOperation 队列默认是串行的。您的 while 循环只是永远旋转(因为该操作永远不会取消),并且对您的委托方法的调用位于它后面的队列中,永远无法运行。

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

于 2013-08-25T14:28:25.560 回答