IT story

NSOperationQueue가 모든 작업을 완료하면 알림 받기

hot-time 2020. 9. 6. 12:04
반응형

NSOperationQueue가 모든 작업을 완료하면 알림 받기


NSOperationQueuewaitUntilAllOperationsAreFinished있지만 동기식으로 기다리고 싶지 않습니다. 대기열이 완료되면 UI에서 진행률 표시기를 숨기고 싶습니다.

이를 수행하는 가장 좋은 방법은 무엇입니까?

NSOperations 에서 알림을 보낼 수 없습니다. 어떤 알림 이 마지막 [queue operations]일지 모르고 알림이 수신 될 때 아직 비어 있지 않을 수 있습니다 (또는 더 나쁘게-다시 채워짐).


KVO를 사용 operations하여 대기열 속성 을 관찰 한 다음을 확인하여 대기열이 완료되었는지 알 수 있습니다 [queue.operations count] == 0.

KVO를 수행하는 파일의 어딘가에 다음과 같이 KVO에 대한 컨텍스트를 선언하십시오 ( 추가 정보 ).

static NSString *kQueueOperationsChanged = @"kQueueOperationsChanged";

대기열을 설정할 때 다음을 수행하십시오.

[self.queue addObserver:self forKeyPath:@"operations" options:0 context:&kQueueOperationsChanged];

그런 다음 다음에서 수행하십시오 observeValueForKeyPath.

- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object 
                         change:(NSDictionary *)change context:(void *)context
{
    if (object == self.queue && [keyPath isEqualToString:@"operations"] && context == &kQueueOperationsChanged) {
        if ([self.queue.operations count] == 0) {
            // Do something here when your queue has completed
            NSLog(@"queue has completed");
        }
    }
    else {
        [super observeValueForKeyPath:keyPath ofObject:object 
                               change:change context:context];
    }
}

(이것은 귀하가라는 NSOperationQueue속성에 있다고 가정합니다 queue)

객체가 완전히 할당 해제되기 전 (또는 대기열 상태에 대한 관리를 중지 할 때) 다음과 같이 KVO에서 등록을 취소해야합니다.

[self.queue removeObserver:self forKeyPath:@"operations" context:&kQueueOperationsChanged];


부록 : iOS 4.0에는 NSOperationQueue.operationCount문서에 따르면 KVO를 준수 하는 속성이 있습니다. 이 답변은 iOS 4.0에서도 여전히 작동하므로 이전 버전과의 호환성에 여전히 유용합니다.


이 동작과 일치하는 것을 기대하거나 원하는 경우 :

t=0 add an operation to the queue.  queueucount increments to 1
t=1 add an operation to the queue.  queueucount increments to 2
t=2 add an operation to the queue.  queueucount increments to 3
t=3 operation completes, queuecount decrements to 2
t=4 operation completes, queuecount decrements to 1
t=5 operation completes, queuecount decrements to 0
<your program gets notified that all operations are completed>

많은 "짧은"작업이 대기열에 추가되는 경우이 동작이 대신 표시 될 수 있습니다 (작업이 대기열에 추가되는 과정에서 시작되기 때문).

t=0  add an operation to the queue.  queuecount == 1
t=1  operation completes, queuecount decrements to 0
<your program gets notified that all operations are completed>
t=2  add an operation to the queue.  queuecount == 1
t=3  operation completes, queuecount decrements to 0
<your program gets notified that all operations are completed>
t=4  add an operation to the queue.  queuecount == 1
t=5  operation completes, queuecount decrements to 0
<your program gets notified that all operations are completed>

내 프로젝트에서 일련의 NSOperationQueue (즉, maxConcurrentOperationCount = 1)에 많은 작업이 추가 된 후 모든 작업이 완료되었을 때만 마지막 작업이 완료된시기를 알아야했습니다.

인터넷 검색에서 "직렬 NSoperationQueue FIFO입니까?"라는 질문에 대한 응답으로 Apple 개발자로부터이 설명을 찾았습니다. -

모든 작업의 ​​우선 순위가 같고 (작업이 대기열에 추가 된 후 변경되지 않음) 모든 작업이 작업 대기열에 들어갈 때 항상 isReady == YES이면 직렬 NSOperationQueue는 FIFO입니다.

Chris Kane Cocoa Frameworks, Apple

제 경우에는 마지막 작업이 대기열에 추가 된시기를 알 수 있습니다. 따라서 마지막 작업이 추가 된 후 우선 순위가 낮은 다른 작업을 대기열에 추가합니다.이 작업은 대기열이 비 었음을 알리는 알림 만 전송합니다. Apple의 진술이 주어지면 모든 작업이 완료된 후에 만 ​​단일 알림 만 전송됩니다.

마지막 작업을 감지 할 수없는 방식 (즉, 비 결정적)으로 작업이 추가되는 경우 위에서 언급 한 KVO 접근 방식을 사용하고 추가 가드 로직을 추가하여 추가 여부를 감지해야한다고 생각합니다. 작업이 추가 될 수 있습니다.

:)


다른 모든 것에 의존하는 NSOperation을 추가하여 마지막으로 실행하는 것은 어떻습니까?


한 가지 대안은 GCD를 사용하는 것입니다. 참조 참조한다.

dispatch_queue_t queue = dispatch_get_global_queue(0,0);
dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group,queue,^{
 NSLog(@"Block 1");
 //run first NSOperation here
});

dispatch_group_async(group,queue,^{
 NSLog(@"Block 2");
 //run second NSOperation here
});

//or from for loop
for (NSOperation *operation in operations)
{
   dispatch_group_async(group,queue,^{
      [operation start];
   });
}

dispatch_group_notify(group,queue,^{
 NSLog(@"Final block");
 //hide progress indicator here
});

이것이 내가하는 방법이다.

대기열을 설정하고 Operations 속성의 변경 사항을 등록합니다.

myQueue = [[NSOperationQueue alloc] init];
[myQueue addObserver: self forKeyPath: @"operations" options: NSKeyValueObservingOptionNew context: NULL];

... 그리고 관찰자 (이 경우 self)는 다음을 구현합니다.

- (void) observeValueForKeyPath:(NSString *) keyPath ofObject:(id) object change:(NSDictionary *) change context:(void *) context {

    if (
        object == myQueue
        &&
        [@"operations" isEqual: keyPath]
    ) {

        NSArray *operations = [change objectForKey:NSKeyValueChangeNewKey];

        if ( [self hasActiveOperations: operations] ) {
            [spinner startAnimating];
        } else {
            [spinner stopAnimating];
        }
    }
}

- (BOOL) hasActiveOperations:(NSArray *) operations {
    for ( id operation in operations ) {
        if ( [operation isExecuting] && ! [operation isCancelled] ) {
            return YES;
        }
    }

    return NO;
}

이 예에서 "spinner"는 UIActivityIndicatorView어떤 일이 일어나고 있음을 보여줍니다. 당연히 당신은 맞게 바꿀 수 있습니다 ...


operationCount속성 을 관찰하기 위해 KVO를 사용하는 것은 어떻습니까? 그런 다음 대기열이 비었을 때와 비우기를 멈출 때에 대해 듣게됩니다. 진행률 표시기를 다루는 것은 다음과 같이 간단 할 수 있습니다.

[indicator setHidden:([queue operationCount]==0)]

다음과 같은 마지막 작업을 추가합니다.

NSInvocationOperation *callbackOperation = [[NSInvocationOperation alloc] initWithTarget:object selector:selector object:nil];

그래서:

- (void)method:(id)object withSelector:(SEL)selector{
     NSInvocationOperation *callbackOperation = [[NSInvocationOperation alloc] initWithTarget:object selector:selector object:nil];
     [callbackOperation addDependency: ...];
     [operationQueue addOperation:callbackOperation]; 

}

나는 이것을하기 위해 카테고리를 사용하고있다.

NSOperationQueue + Completion.h

//
//  NSOperationQueue+Completion.h
//  QueueTest
//
//  Created by Artem Stepanenko on 23.11.13.
//  Copyright (c) 2013 Artem Stepanenko. All rights reserved.
//

typedef void (^NSOperationQueueCompletion) (void);

@interface NSOperationQueue (Completion)

/**
 * Remarks:
 *
 * 1. Invokes completion handler just a single time when previously added operations are finished.
 * 2. Completion handler is called in a main thread.
 */

- (void)setCompletion:(NSOperationQueueCompletion)completion;

@end

NSOperationQueue + Completion.m

//
//  NSOperationQueue+Completion.m
//  QueueTest
//
//  Created by Artem Stepanenko on 23.11.13.
//  Copyright (c) 2013 Artem Stepanenko. All rights reserved.
//

#import "NSOperationQueue+Completion.h"

@implementation NSOperationQueue (Completion)

- (void)setCompletion:(NSOperationQueueCompletion)completion
{
    NSOperationQueueCompletion copiedCompletion = [completion copy];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self waitUntilAllOperationsAreFinished];

        dispatch_async(dispatch_get_main_queue(), ^{
            copiedCompletion();
        });
    });
}

@end

사용법 :

NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    // ...
}];

NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    // ...
}];

[operation2 addDependency:operation1];

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation1, operation2] waitUntilFinished:YES];

[queue setCompletion:^{
    // handle operation queue's completion here (launched in main thread!)
}];

출처 : https://gist.github.com/artemstepanenko/7620471


ReactiveObjC를 사용하면 잘 작동합니다.

// skip 1 time here to ignore the very first call which occurs upon initialization of the RAC block
[[RACObserve(self.operationQueue, operationCount) skip:1] subscribeNext:^(NSNumber *operationCount) {
    if ([operationCount integerValue] == 0) {
         // operations are done processing
         NSLog(@"Finished!");
    }
}];

참고로, 당신은 신속한 3 에서 GCD dispatch_group 으로 이것을 달성 할 수 있습니다 . 모든 작업이 완료되면 알림을받을 수 있습니다.

let group = DispatchGroup()

    group.enter()
    run(after: 6) {
      print(" 6 seconds")
      group.leave()
    }

    group.enter()
    run(after: 4) {
      print(" 4 seconds")
      group.leave()
    }

    group.enter()
    run(after: 2) {
      print(" 2 seconds")
      group.leave()
    }

    group.enter()
    run(after: 1) {
      print(" 1 second")
      group.leave()
    }


    group.notify(queue: DispatchQueue.global(qos: .background)) {
      print("All async calls completed")
}

새를 만들 NSThread거나 백그라운드에서 선택기를 실행하고 거기서 기다릴 수 있습니다. NSOperationQueue완료 되면 자신의 알림을 보낼 수 있습니다.

나는 다음과 같은 것을 생각하고 있습니다.

- (void)someMethod {
    // Queue everything in your operationQueue (instance variable)
    [self performSelectorInBackground:@selector(waitForQueue)];
    // Continue as usual
}

...

- (void)waitForQueue {
    [operationQueue waitUntilAllOperationsAreFinished];
    [[NSNotificationCenter defaultCenter] postNotification:@"queueFinished"];
}

작업 을 기본 클래스로 사용하면 whenEmpty {}블록을 OperationQueue에 전달할 수 있습니다 .

let queue = OOperationQueue()
queue.addOperation(op)
queue.addOperation(delayOp)

queue.addExecution { finished in
    delay(0.5) { finished() }
}

queue.whenEmpty = {
    print("all operations finished")
}

KVO없이

private let queue = OperationQueue()

private func addOperations(_ operations: [Operation], completionHandler: @escaping () -> ()) {
    DispatchQueue.global().async { [unowned self] in
        self.queue.addOperations(operations, waitUntilFinished: true)
        DispatchQueue.main.async(execute: completionHandler)
    }
}

참고 URL : https://stackoverflow.com/questions/1049001/get-notification-when-nsoperationqueue-finishes-all-tasks

반응형