在NSOperation中使用代表

The*_*imp 5 objective-c nsoperation cllocationmanager ios

我正在努力利用CLLocationManager一个NSOperation.作为其中的一部分,我需要能够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;
}
Run Code Online (Sandbox Code Playgroud)

Nat*_*nes 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]];
}
Run Code Online (Sandbox Code Playgroud)

我关键isFinished,我通过设置器更新isCancelledisFinished.这是isCancelledsetter的一个例子:

- (void)setIsCancelled:(BOOL)isCancelled {
    _isCancelled = isCancelled;
    if (_isCancelled == YES) {
        self.isFinished = YES;
    }
}
Run Code Online (Sandbox Code Playgroud)

那就是说,我提出了一些关于为什么这是必要的问题.如果在找到位置之前不需要启动某些东西,为什么不在主线程上启动位置管理器,等待相应的委托回调然后启动后台操作?

更新:更新解决方案

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

文艺青年最爱的

更改

[[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
Run Code Online (Sandbox Code Playgroud)

[[NSRunLoop currentRunLoop] runMode:NSRunLoopCommonModes
                         beforeDate:[NSDate distantFuture]];
Run Code Online (Sandbox Code Playgroud)

细节

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

@property(nonatomic,assign,readonly) BOOL isExecuting;
@property(nonatomic,assign,readonly) BOOL isFinished;
@property(nonatomic,assign,readonly) BOOL isCancelled;
Run Code Online (Sandbox Code Playgroud)

在您的操作实现中,您将要进行这样的readwrite,如下所示:

@interface NJBaseOperation ()

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

@end
Run Code Online (Sandbox Code Playgroud)

接下来,您将要合成上面定义的三个属性,以便您可以覆盖setter并使用它们来管理您的操作状态.这是我通常使用的,但有时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;
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,为了使我们不必手动发送KVO通知,我们将实现以下方法.这是有效的,因为我们的属性已命名isExecuting,isFinished并且isCancelled.

+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key {
    return YES;
}
Run Code Online (Sandbox Code Playgroud)

现在,操作基础已经完成,是时候淘汰位置了.您将要覆盖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]];
    }
}
Run Code Online (Sandbox Code Playgroud)

您应该收到一个委托回调,此时您可以根据位置进行一些工作,然后完成操作.这是我的实现,计入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;
}
Run Code Online (Sandbox Code Playgroud)

GitHub上的源包含一个dealloc实现,它只是记录它被调用,并且还观察到operationCount对我的更改NSOperationQueue并记录计数 - 指示它何时回落到0.希望有所帮助.如果您有疑问,请告诉我.


ato*_*irk 0

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

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