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
어떤 일이 일어나고 있음을 보여줍니다. 당연히 당신은 맞게 바꿀 수 있습니다 …
답변
나는 이것을하기 위해 카테고리를 사용하고있다.
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!)
}];
답변
현재 아이폰 OS 13.0 의 operationCount 및 동작 특성은 사용되지 않습니다. 대기열의 작업 수를 직접 추적하고 작업 이 모두 완료되면 알림 을 실행하는 것도 간단합니다 . 이 예제는 Operation 의 비동기 서브 클래 싱에서도 작동 합니다.
class MyOperationQueue: OperationQueue {
public var numberOfOperations: Int = 0 {
didSet {
if numberOfOperations == 0 {
print("All operations completed.")
NotificationCenter.default.post(name: .init("OperationsCompleted"), object: nil)
}
}
}
public var isEmpty: Bool {
return numberOfOperations == 0
}
override func addOperation(_ op: Operation) {
super.addOperation(op)
numberOfOperations += 1
}
override func addOperations(_ ops: [Operation], waitUntilFinished wait: Bool) {
super.addOperations(ops, waitUntilFinished: wait)
numberOfOperations += ops.count
}
public func decrementOperationCount() {
numberOfOperations -= 1
}
}
아래는 쉬운 비동기 작업을위한 Operation의 하위 클래스입니다.
class AsyncOperation: Operation {
let queue: MyOperationQueue
enum State: String {
case Ready, Executing, Finished
fileprivate var keyPath: String {
return "is" + rawValue
}
}
var state = State.Ready {
willSet {
willChangeValue(forKey: newValue.keyPath)
willChangeValue(forKey: state.keyPath)
}
didSet {
didChangeValue(forKey: oldValue.keyPath)
didChangeValue(forKey: state.keyPath)
if state == .Finished {
queue.decrementOperationCount()
}
}
}
override var isReady: Bool {
return super.isReady && state == .Ready
}
override var isExecuting: Bool {
return state == .Executing
}
override var isFinished: Bool {
return state == .Finished
}
override var isAsynchronous: Bool {
return true
}
public init(queue: MyOperationQueue) {
self.queue = queue
super.init()
}
override func start() {
if isCancelled {
state = .Finished
return
}
main()
state = .Executing
}
override func cancel() {
state = .Finished
}
override func main() {
fatalError("Subclasses must override main without calling super.")
}
}