Operation

Horus-iOS·2023년 4월 8일
0

https://developer.apple.com/documentation/foundation/operation

An abstract class that represents the code and data associated with a single task.

단일 작업에 연관된 코드 및 데이터를 나타내는 추상 클래스입니다.

Declaration

class Operation : NSObject

Overview

오퍼레이션 클래스는 추상 클래스이기 때문에 직접 사용하지 않아야 하며, 실제 작업을 수행하려면 하위 클래스를 만들거나 시스템에 의해 정의된 하위 클래스(NSInvocationOperation 혹은 BlockOperation) 중 한 가지를 사용해야 합니다. 추상화되어 있음에도 오퍼레이션의 기본 구현은 작업의 안전한 수행을 조직화할 수 있는 중요한 로직을 포함합니다. 내재된 로직은 다른 시스템 객체와 정확히 작동하는지 확인해야 하는 글루 코드가 아닌, 작업 실제 구현에 초점을 맞출 수 있도록 합니다.

오퍼레이션 객체는 단일(샷 객체)이며, 즉 가지고 있는 작업을 한 번 수행합니다. 그리고 다시 수행하기 위해 사용될 수는 없습니다. 일반적으로 오퍼레이션 큐(OperationQueue 클래스 인스턴스)에 오퍼레이션을 추가하는 방법으로 오퍼레이션을 수행하게 됩니다. 오퍼레이션 큐는 두 번째 스레드에서 실행함으로써 직접 오퍼레이션을 수행하거나 libdispatch 라이브러리(GCD: Grand Central Dispatch라고 알려진)를 간접적으로 사용해서 오퍼레이션을 수행합니다. 큐가 어떻게 오퍼레이션을 수행하는지에 대한 더 많은 정보는 OperationQueue를 보시기 바랍니다.

OperationQueue
https://developer.apple.com/documentation/foundation/operationqueue

오퍼레이션 큐를 사용하지 않으려면 코드에서 직접적으로 start() 메소드를 호출해 오퍼레이션을 수행할 수 있습니다. 오퍼레이션을 직접 수행하는 것은 코딩에 더 많은 부담이 생길 것이며, 그 이유는 준비 상태가 아닌 오퍼레이션을 시장하는 것은 예외를 발생시키기 때문입니다. isReady 속성은 오퍼레이션의 준비 상태를 알려줍니다.

Operation Dependencies

종속성은 정해놓은 순서로 오퍼레이션을 수행하게 해주는 간편한 방법입니다. addDependency(_:), removeDependency(_:) 메소드를 사용해서 종속성을 추가, 제거할 수 있습니다. 종속성을 갖는 오퍼레이션 객체는 모든 종속 오퍼레이션 객체가 수행을 마치 전까지 준비 상태가 아닙니다. 그러나 마지막 종속 오퍼레이션이 완료되면 오퍼레이션 객체는 준비 상태가 되고 수행할 수 있게 됩니다.

NSOperation이 지원하는 종속성은 종속 오퍼레이션이 성공적으로 마쳤는지 아닌지를 구분하지 않습니다. (오퍼레이션 취소 역시 마무리된 것과 유사하게 표시합니다.) 종속 오퍼레이션의 취소, 작업이 성공적으로 완료되지 않은 경우 각각에 따라 종속성을 갖는 다른 오퍼레이션이 지속적으로 수행되어야 하는지 여부는 직접 구현해야 합니다. 몇 가지 추가적인 오류 트래킹을 오퍼레이션 객체에 통합해야 하기도 합니다.

KVO-Compliant Properties

NSOperation 클래스의 여러 속성은 키 값 코딩이며, 키 값 옵저빙을 준수합니다. 필요한 경우 애플리케이션의 다른 부분을 제어하고자 이와 같은 속성들을 관찰할 수 있습니다. 속성을 관찰하려면 아래 키 경로를 사용합니다.

  • isCancelled - read-only
  • isAsynchronous - read-only
  • isExecuting - read-only
  • isFinished - read-only
  • isReady - read-only
  • dependencies - read-only
  • queuePriority - readable and writable
  • completionBlock - readable and writable

이와 같은 속성에 옵저버를 붙일 수 있기도 하지만, 애플리케이션 UI의 요소에 속성을 바인드하려고 할 때 코코아 바인딩을 사용하지 않아야 합니다. UI에 연관된 코드는 일반적으로 애플리케이션의 메인 스레드에서 수행되어야 합니다. 오퍼레이션은 모든 스레드에서 수행될 것이기 때문에 해당 오퍼레이션과 관련이 있는 KVO 노티피케이션도 모든 스레드에서 발생합니다.

선행하는 속성에 커스텀 구현을 하려면 구현은 KVC, KVO를 준수해야 합니다. NSOperation 객체에 추가적인 속성을 정의하려면 이 역시 KVC, KVO를 준수하길 권장합니다. 키 값 코딩을 지원하는 방법에 대한 정보는 Key-Value Coding Programming Guide를 보시기 바랍니다. 키 값 옵저빙을 지원하는 방법에 대한 정보는 Key-Value Observing Programming Guide를 보시기 바랍니다.

Key-Value Coding Programming Guide
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueCoding/index.html#//apple_ref/doc/uid/10000107i

Key-Value Observing Programming Guide
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html#//apple_ref/doc/uid/10000177i

Multicore Considerations

NSOperation 클래스는 그 자체로 멀티코어를 인식합니다. 그렇기 때문에 객체에 동기화 접근을 할 수 있는 추가적인 락 생성 없이 여러 스레드로부터 NSOperation 객체의 메소드를 호출하는 것이 안전합니다. 이 동작은 필수적이며 오퍼레이션은 일반적으로 다른 오퍼레이션과 분리된 스레드에서 실행되기 때문입니다.

NSOperation의 하위 클래스를 생성할 때, 모든 오버라이드 메소드가 여러 스레드로부터 호출할 수 있도록 안전한 상태로 만들어야 합니다. 커스텀 데이터 접근자와 같은 커스텀 메소드를 하위 클래스에 구현하려면, 이와 같은 메소드가 스레드 안전한 상태여야 합니다. 그러므로 오퍼레이션에서 모든 데이터 변수 접근은 데이터 손상을 방지할 수 있도록 동기화되어야 합니다. 동기화에 관한 더 많은 정보는 Threading Programming Guide를 보시기 바랍니다.

Threading Programming Guide
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/Introduction/Introduction.html#//apple_ref/doc/uid/10000057i

Asynchronous Versus Synchronous Operations

오퍼레이션 객체를 직접 수행하려면 큐에 추가하는 것이 아니라 동기 혹은 비동기 방식으로 수행하기 위한 오퍼레이션을 디자인할 수 있습니다. 오퍼레이션 객체는 동기 방식이 기본값입니다. 동기 방식의 오퍼레이션에서 오퍼레이션 객체는 자신의 작업이 수행될 분리된 스레드를 생성하지 않습니다. 동기 오퍼레이션의 start() 메소드를 직접 호출하면, 오퍼레이션은 즉시 현재 스레드에서 수행합니다. 객체의 start() 메소드가 호출자에게 제어건을 반환할 때까지 작업 자체가 완료됩니다.

비동기 오퍼레이션의 start() 메소드를 호출하면, 해당 메소드는 상응하는 작업이 완료되기 전에 반환될 것입니다. 비동기 오퍼레이션 객체는 분리된 스레드에서 작업의 스케줄링에 책임이 있습니다. 오퍼레이션은 새로운 스레드를 직접 시작, 비동기 메소드를 호출, 수행에 대한 디스패치 큐에 블록을 제출함으로써 스케줄링을 할 수 있습니다. 제어가 호출자에게 반환할 때 오퍼레이션이 진행중인지 여부는 실제로 중요하지 않으며 진행중일 수 있다는 사실만 중요합니다.

오퍼레이션을 수행하고자 항상 큐를 사용하려고 한다면, 동기 방식으로 정의하는 것이 비교적 더 간단합니다. 오퍼레이션을 직접 수행하려면 비동기 방식의 오퍼레이션 객체를 정의하길 원할 것입니다. 비동기 오퍼레이션을 정의하는 것은 더 많은 작업을 요구하며, 작업의 진행 상태를 모니터링 해야 하고 KVO 노티피케이션을 사용해서 해당 상태에 대한 변화를 알려야하기 때문입니다. 비동기 오퍼레이션을 정의하는 것은 수행되는 직접 생성한 오퍼레이션이 스레드 호출을 막지 않는 것을 보장하길 원할 때 유용합니다.

오퍼레이션 큐에 오퍼레이션을 추가할 때 큐는 isAsynchronous 속성의 값을 무시하며 항상 분리된 스레으로부터 start() 메소드를 호출합니다. 그러므로 항상 오퍼레이션 큐에 오퍼레이션을 추가하는 방법으로 오퍼레이션을 실행하려고 한다면, 비동기 방식으로 만들 이유는 없습니다.

동기 오퍼레이션과 비동기 오퍼레이션을 정의하는 방법에 대한 내용은 아래에 있는 Subclassing Notes를 보시기 바랍니다.

Subclassing Notes

NSOperation 클래스는 오퍼레이션의 수행 상태를 추적하는 기본적인 로직을 제공합니다. 하지만 실제 작동을 하려면 서브클래싱이 필요합니다. 서브클래싱 생성 방법은 오퍼레이션이 동시성 혹은 동시성이 아닌 상태로 수행되는 오퍼레이션인지에 따라 결정됩니다.

Methods to Override

동시성이 아닌 오퍼레이션은 일반적으로 하나의 메소드만 오버라이드 합니다.

  • main()

이 메소드에 필요한 작업을 수행할 수 있는 코드를 작성해야 합니다. 커스텀 클래스의 인스턴스 생성을 더 쉽게 할 수 있도록 커스텀 초기화 메소드를 정의해야 하기도 합니다. 오퍼레이션으로부터 데이터에 접근하기 위해 getter, setter 메소드를 정의하는 경우가 필요할 수도 있습니다. 그러나 커스텀 getter, setter 메소드를 정의하려면 해당 메소드가 다중 스레드로부터 안전하게 호출될 수 있도록 해야 합니다.

동시성 오퍼레이션을 생성하려면 최소한 아래 메소드와 속성을 오버라이드해야 합니다.

  • start()
  • isAsynchronous
  • isExecuting
  • isFinished

동시성 오퍼레이션에서 start() 메소드는 비동기 방식으로 오퍼레이션이 시작되어야 하는 책임이 있습니다. 스레드를 생성하거나 비동기 함수를 호출하는 것에 따라 이 메소드를 통해 그렇게 해야 합니다. 오퍼레이션을 시작하면 start() 메소드는 isExecuting 속성에 의해 정보가 알려질 때 오퍼레이션의 수행 상태를 업데이트해야 합니다. 관련 클라이언트가 오퍼레이션이 실행중이라는 것을 알 수 있도록 해주는 isExecuting 키 경로에서 KVO 노티피케이션을 보냄으로써 그렇게 할 수 있습니다. isExecuting 속성은 스레드 안전한 방식으로 상태를 제공해야 합니다.

테스크가 컴플리션 혹은 취소되면 동시성 오퍼레이션 객체는 오퍼레이션에 대한 최종 상태 변화를 표시할 수 있도록 isExecuting, isFinished 키 경로에 KVO 노티피케이션을 생성해야 합니다. (오퍼레이션이 작업을 완전히 마치지 않았을 때도 취소된 경우는 계속 isFinished 키 경로를 업데이트해야 합니다. 대기 중인 작업은 큐에서 제거되기 전에 완료되었음을 알려야 합니다.) KVO 노티피케이션을 생성하는 것과 더불어 isExecuting, isFinished 속성의 오버라이드는 오퍼레이션의 상태에 기반한 정확한 값을 지속해서 알려줘야 합니다.

동시성 오퍼레이션을 정의 하는 방법은 Concurrency Programming Guide를 보시기 바랍니다.

Concurrency Programming Guide
https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008091

Important
start() 메소드에서 super를 호출하지 않아야 합니다. 동시성 오퍼레이션을 정의하는 경우 기본값 start() 메소드가 제공하는 것과 동일한 동작을 제공해야 하며, 작업의 시작, 적합한 KVO 노티피케이션 생성을 포함하는 것이어야 합니다. 작업 시작의 실제 시작 전에 start() 메소드는 오퍼레이션 자체가 취소되었는지 확인해야 합니다. 취소 시맨틱에 대한 더 많은 정보는 Responding to the Cancel Command를 보시기 바랍니다.

Responding to the Cancel Command
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/Introduction/Introduction.html#//apple_ref/doc/uid/10000057i

동시성 오퍼레이션마저도 위에 설명한 것들처럼 약간의 메소드 오버라이드가 필요합니다. 그러나 오퍼레이션의 종속성 기능을 커스터마이징한다면, 추가적인 메소드 오버라이드, KVO 노티피케이션 제공이 필요합니다. 종속성의 경우 isReady 키 경로에 대한 노티피케이션 제공만을 요구합니다. 종속성 속성은 의존 오퍼레이션의 리스트를 포함하기 때문에 이를 변경하는 것은 이미 기본값 NsOperation 클래스에서 처리됩니다.

Maintaining Operation Object States

오퍼레이션 객체는 수행하기에 안전한, 오퍼레이션이 라이프 사이클을 통한 진행을 외부 클라이언트에게 알리기 위한 시점을 결정하는 상태 정보를 내부적으로 유지합니다. 커스텀 하위 클래스는 코드에서 오퍼레이션의 정확한 수행을 보장하기 위한 상태 정보를 유지합니다. 오퍼레이션의 상태와 관련이 있는 키 경로는 아래와 같습니다.

isReady
isReady 키 경로는 언제 오퍼레이션이 수행할 준비가 되었는지를 클라이언트가 알 수 있도록 해줍니다. isReady 속성은 오퍼레이션이 당장 수행할 수 있을 때 true 값을 포함하고 있거나 종속성에 따라 아직 완료되지 않은 오퍼레이션이 존재할 때 false 값을 포함하게 됩니다.

대부분의 경우 이 키 경로의 상태를 직접 다룰 필요는 없습니다. 그러나 오퍼레이션의 준비 상태가 다른 종속 오퍼레이션이 아닌 다른 요소에 의해 결정되는 경우(프로그램에서 외적 특정 외부 컨디션과 같은) isReady 속성 구현을 제공할 수 있고, 오퍼레이션의 준비 상태를 추적할 수 있습니다. 외부 상태가 허용할 때 단순히 오퍼레이션 객체만을 생성하는 것이 비교적 간단합니다.

맥OS 10.6 이상의 운영체제부터 하나 이상의 종속적 오퍼레이션의 완료를 기다리는 중 오퍼레이션을 취소하려면 종속성은 이후 무시되고, 이 속성의 값은 바로 실행하기에 준비 상태인 것을 거절하기 위해 업데이트됩니다. 이와 같은 동작은 오퍼레이션 큐에게 취소된 오퍼레이션을 더 빠르게 플러시할 수 있는 기회를 줍니다.

isExecuting
isExecuting 키 경로는 오퍼레이션이 할당된 작업에 있어 활성화되어 동작중인지를 클라이언트가 알 수 있도록 합니다. isExecuting 속성은 오퍼레이션이 작업에 대한 수행을 하고 있을 경우 true 값을 알려야 하고, 그렇지 않은 경우 false 값을 알려야 합니다. 오퍼레이션 객체의 start() 메소드를 대체하는 경우 isExecuting 속성도 대체해야 하며 오퍼레이션 변경의 수행 상태가 변할 때 KVO 노티피케이션을 생성해야 합니다.

isFinished
isFinished 키 경로는 오퍼레이션이 작업을 성공적으로 마쳤는지 혹은 취소되어 존재하고 있는지를 클라이언트가 알 수 있도록 해줍니다. 오퍼레이션 객체는 isFinished 키 경로에 있는 값이 true가 되기 전까지 종속성을 비우지 않습니다. 유사하게 오퍼레이션 큐는 isFinished 속성이 true 값을 포함하기 전까지 오퍼레이션을 큐에서 제거하지 않습니다. 그러므로 오퍼레이션을 완료된 것으로 표시하는 것은 진행중이거나 취소된 오퍼레이션을 백업하는 것으로부터 큐를 유지하기 위해 중요합니다.

start() 메소드를 대체하거나 오퍼레이션 객체를 대체하려면 isFinished 속성 역시 대체되어야 하고, 오퍼레이션이 수행을 완료하거나 취소될 때 KVO 노티피케이션을 생성해야 합니다.

isCancelled
isCancelled 키 경로는 오퍼레이션의 취소가 요청되었을 때 클라이언트가 알 수 있도록 해줍니다. 취소를 지원하는 것은 자발적이지만 권장되며, 이 키 경로에 KVO 노티피케이션을 보낼 필요는 없습니다. 오퍼레이션에서 취소 알림의 처리는 아래에 있는 Responding to the Cancel Command에 설명되어 있습니다.

Responding to the Cancel Command

큐에 오퍼레이션을 추가하면 오퍼레이션은 개발자의 손을 떠나게 됩니다. 큐는 해당 작업을 받고 스케줄링을 처리합니다. 그러나 이후 오퍼레이션을 수행하지 않기를 원할 때(예를 들어 사용자가 취소 버튼을 누르거나 애플리케이션을 종료하는 등) CPU 시간 소모를 방지하기 위해 오퍼레이션을 취소할 수 있습니다. 오퍼레이션 객체 자체의 cancel() 메소드를 호출하거나 OperationQueue 클래스의 cancelAllOperations() 메소드를 호출해서 취소할 수 있습니다.

오퍼레이션을 취소하는 것은 하고 있는 것을 멈추는 것을 즉각적으로 강제하지 않습니다. 모든 오퍼레이션에서 isCancelled 속성을 준수해야 하지만, 코드는 명시적으로 이 값을 확인해야 하며 필요한 경우 중단되어야 합니다. NSOperation의 기본값 구현은 취소에 대한 확인을 포함합니다. 예를 들어 start() 메소드 호출 전에 오퍼레이션을 취소하면, start() 메소드는 작업 시작이 없는 상태로 존재합니다.

Note
맥OS 10.6 이상의 운영체제에서 오퍼레이션 큐에 있는, 종속 오퍼레이션이 마무리되지 않은 오퍼레이션에 cancel() 메소드를 호출하면, 종속 오퍼레이션은 이후에 무시됩니다. 오퍼레이션은 이미 취소되기 때문에 이와 같은 동작은 큐가 오퍼레이션의 start() 메소드를 호출할 수 있도록 해주며, main() 메소드 호출 없이 큐로부터 오퍼레이션을 제거하기 위함입니다. 큐에 있지 않은 오퍼레이션에 cancel() 메소드를 호출하면 오퍼레이션은 즉시 취소된 상태로 표시됩니다. 각각의 경우에서 오퍼레이션을 준비 혹은 완료된 상태로 표시하는 것은 적합한 KVO 노티피케이션의 생성 결과를 내보냅니다.

작성하는 모든 커스텀 코드에 취소 시맨틱을 지원해야 합니다. 특히 주요 작업 코드는 isCancelled 속성의 값을 주기적으로 확인해야 합니다. 만약 속성이 true 값을 알려주고 있다면 오퍼레이션 객체는 비워져야 하고, 가능한 빠르게 종료되어야 합니다. 커스텀 start() 메소드를 구현하려면 해당 메소드가 취소에 대한 빠른 확인을 포함해야 하고 적합한 동작이 이뤄지도록 해야 합니다. 커스텀 start() 메소드는 빠른 취소의 타입을 처리하기 위해 준비되어야 합니다.

오퍼레이션 취소 시 단순히 종료하는 것과 더불어 취소된 오퍼레이션을 적합한 마지막 상태로 이동시키는 것 역시 중요합니다. isFinished, isExecuting 속성 값을 직접 다루는 경우(아마도 동시성 오퍼레이션을 구현하게 될 때) 이 속성들도 상황에 맞게 업데이트해야 합니다. isFinished에 의해 반환되는 값을 true로 변경해야 하고, isExecuting에 의해 반환되는 값을 false로 변경해야 합니다. 오퍼레이션이 수행 시작 전일지라도 취소되었다면 이 속성들을 변경해줘야 합니다.

0개의 댓글