Use of delegates in NSOperation

后端 未结 4 774
滥情空心
滥情空心 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-10 00:06

    Going back to the runloop discussion, this is how I generally solve that in my base NSOperation implementation:

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

    I key off of isFinished, which I keep updated through setters for isCancelled and isFinished. Here's the isCancelled setter as an example:

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

    That said, I second some of the questions about why this is necessary. If you don't need to kick something off until a location is found, why not just fire up your location manager on the main thread, wait for the appropriate delegate callback and then kick off the background operation?

    Update: updated solution

    While the original answer generally stands, I've fully implement a solution and it does require a slight change to how you manage the run loop. That said, all code is available on GitHub - https://github.com/nathanhjones/CLBackgroundOperation. Here is a detailed explanation of the approach.

    Tl;dr

    Change

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

    to

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

    Details

    Within your operations interface define the following three properties. We'll be indicating that these operations are concurrent thus we'll manage their state manually. In the solution on GitHub these are part of NJBaseOperation.

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

    Within your operations implementation you'll want to make those readwrite like so:

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

    Next, you'll want to synthesize the three properties you defined above so that you can override the setters and use them to manage your operations state. Here's what I generally use, but sometimes there are some additional statements added to the setIsFinished: method depending on my needs.

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

    Lastly, just so that we don't have to manually send the KVO notifications we'll implement the following method. This works because our properties are named isExecuting, isFinished and isCancelled.

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

    Now that the the operations foundation is taken care of it's time to knockout the location stuff. You'll want to override main and within it fire up your location manager and instruct the current run loop to keep running until you tell it otherwise. This ensures that your thread is around to receive the location delegate callbacks. Here's my implementation:

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

    You should receive a delegate callback at which point you can do some work based on location and then finish the operation. Here's my implementation that counts to 10,000 and then cleans up.

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

    The source on GitHub includes a dealloc implementation, which simply logs that it's being called and also observes changes to the operationCount of my NSOperationQueue and logs the count - to indicating when it drops back to 0. Hope that helps. Let me know if you've got questions.

提交回复
热议问题