NSOperationQueue가 모든 작업을 완료하면 알림 받기
NSOperationQueue
가 waitUntilAllOperationsAreFinished
있지만 동기식으로 기다리고 싶지 않습니다. 대기열이 완료되면 UI에서 진행률 표시기를 숨기고 싶습니다.
이를 수행하는 가장 좋은 방법은 무엇입니까?
내 NSOperation
s 에서 알림을 보낼 수 없습니다. 어떤 알림 이 마지막 [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)
}
}
'IT story' 카테고리의 다른 글
Swift의 상태 표시 줄 높이 (0) | 2020.09.06 |
---|---|
RxJava에서 Observable, Completable 및 Single의 차이점은 무엇입니까? (0) | 2020.09.06 |
.NET에는 List a에 List b의 모든 항목이 포함되어 있는지 확인할 수있는 방법이 있습니까? (0) | 2020.09.06 |
Ruby 스크립트 내에서 명령 줄 명령 실행 (0) | 2020.09.06 |
방화범을 사용하여 전체 프로그램에 대한 기능 로그 / 스택 추적 인쇄 (0) | 2020.09.06 |