How to implement an NSRunLoop inside an NSOperation

后端 未结 3 666
孤街浪徒
孤街浪徒 2020-12-24 03:13

Im posting this question because I have seen a lot of confusion over this topic and I spent several hours debugging NSOperation subclasses as a result.

The problem i

3条回答
  •  無奈伤痛
    2020-12-24 03:48

    I'm not sure why you would want all the overhead of NSOperation just for a run loop, but I suppose if you are using an operation queue design then maybe it would be useful. The reason I say this is usually you would just perform a selector in background and call CFRunLoopRun from there.

    That aside, below is an example NSOperation subclass that uses a run loop. Just subclass it and override willRun and call your method that requires a run loop to work. Once all methods called have finished, thus all run loop sources have been handled - the operation will end automatically. You can test it out by putting a simple perform selector after delay in the willRun method and a break point in completeOperation and you will see the operation will last as long as it takes to finish performing that. Furthermore, if you were to perform after delay something else at that point then the operation will continue to run. As I said it keeps running as long as there is something that requires a run loop to function, even if those are added after it was started.

    There is no need for a stop method because as soon as everything has finished and there are no more sources to process it will end automatically.

    MHRunLoopOperation.h

    #import 
    
    @interface MHRunLoopOperation : NSOperation
    
    // Override and call methods that require a run loop.
    // No need to call super because the default implementation does nothing.
    -(void)willRun;
    
    @end
    

    MHRunLoopOperation.m

    #import "MHRunLoopOperation.h"
    
    @interface MHRunLoopOperation()
    
    @property (nonatomic, assign) BOOL isExecuting;
    @property (nonatomic, assign) BOOL isFinished;
    
    @end
    
    @implementation MHRunLoopOperation
    
    - (BOOL)isAsynchronous {
        return YES;
    }
    
    - (void)start {
        // Always check for cancellation before launching the task.
        if (self.isCancelled)
        {
            // Must move the operation to the finished state if it is canceled.
            self.isFinished = YES;
            return;
        }
    
        // If the operation is not canceled, begin executing the task.
        [self willChangeValueForKey:@"isExecuting"];
        [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
        _isExecuting = YES;
        [self didChangeValueForKey:@"isExecuting"];
    }
    
    - (void)main {
        @try {
            // Do the main work of the operation here.
    
            [self willRun];
    
            CFRunLoopRun(); // It waits here until all method calls or remote data requests that required a run loop have finished. And after that then it continues.
    
            [self completeOperation];
        }
        @catch(...) {
            // Do not rethrow exceptions.
        }
    }
    
    -(void)willRun{
          // To be overridden by a subclass and this is where calls that require a run loop are done, e.g. remote data requests are started.
    }
    
    -(void)completeOperation{
        [self willChangeValueForKey:@"isFinished"];
        [self willChangeValueForKey:@"isExecuting"];
    
        _isExecuting = NO;
        _isFinished = YES;
    
        [self didChangeValueForKey:@"isExecuting"];
        [self didChangeValueForKey:@"isFinished"];
    }
    
    @end
    

    What the heck, here is an example subclass too :-)

    @interface TestLoop : MHRunLoopOperation
    
    @end
    
    @implementation TestLoop
    
    // override
    -(void)willRun{
        [self performSelector:@selector(test) withObject:nil afterDelay:2];
    }
    
    -(void)test{
        NSLog(@"test");
    
        // uncomment below to make keep it running forever
        //[self performSelector:@selector(test) withObject:nil afterDelay:2];
    }
    
    // overridden just for demonstration purposes 
    -(void)completeOperation{
         NSLog(@"completeOperation");
         [super completeOperation];
    }
    
    @end
    

    Just test it out like this:

    TestLoop* t = [[TestLoop alloc] init];
    [t start];
    

提交回复
热议问题