isReady
상태로 전환되면 시스템은 사용 가능한 스레드를 찾기 시작한다. 스케줄라가 작업을 실행할 스레드를 찾으면 작업은 isExecuting
상태로 전환된다. 그 시점에서 코드가 실행되고 완료되면 상태는 isFinished
가 된다. isFinished
로 전환을 수동으로 해줘야 한다. Operation
클래스를 통해 개발자에게 동기 및 비동기 작업에 대한 기본 틀을 제공하며, 이 클래스는 매우 일반적이고 다양한 사용 사례에 맞게 확장 및 조정이 가능하도록 설계되어져있다. 이렇게 설계하는 것이 개발자에게 더 많은 유연성과 커스터마이징 옵션을 제공할 수 있기 때문이다.AsyncOperation
은 비동기 작업에 대한 상속 클래스로 구현될 수 있으며, 이를 구현하는 것은 상대적으로 간단한 작업이다. 이러한 구현을 제공하지 않음으로써 Apple은 개발자에게 프로젝트 요구 사항에 따라 비동기 작업에 대한 더 세밀한 제어와 필요한 추가 기능을 구현할 수 있는 기회를 제공한다.Operation
클래스를 직접 사용하면서 동기 또는 비동기 작업을 구현할 수 있기 때문에, Apple은 필요에 따라 개발자가 클래스를 확장하고 사용하는 데 필요한 도구를 제공하는 것으로 보여진다. 이것이 바로 Operation
과 같은 고차원의 추상화를 제공하는 이유이다.State
열거형을 class의 맨 윗부분에 생성한다.class AsyncOperation: Operation {
enum State: String {
case ready, executing, finished
// KVO notifications을 위한 keyPath 설정
fileprivate var keyPath: String {
return "is\(rawValue.capitalized)"
}
}
}
KeyPath
는 앞서 언급한 KVO notification을 지원하는 데 도움이 된다. 현재 State 의 keyPath를 요청하면 상태 값의 첫 글자를 대문자로 변경하고 텍스트 is를 접두사로 사용한다. 따라서 실행 중인 경우 keyPath는 isExecuting
을 반환하며, Operation 기본 클래스의 속성과 일치한다. keyPath
는 이 파일 전체에서 사용 가능해야 하지만 외부에서는 사용할 수 없어야 한다. private로 설정하면 열거형 자체 외부에서는 볼 수 없다. var state = State.ready {
// 값이 변경되면,
willSet {
willChangeValue(forKey: newValue.keyPath)
willChangeValue(forKey: state.keyPath)
}
// 값이 변경된 후
didSet {
didChangeValue(forKey: oldValue.keyPath)
didChangeValue(forKey: state.keyPath)
}
}
- will change for
isReady
- will change for
isExecuting
- Did change for
isReady
- Did change for
isExecuting
override var isReady: Bool {
return super.isReady && state == .ready
}
override var isExecuting: Bool {
return state == .executing
}
override var isFinished: Bool {
return state == .finished
}
isReady
메서드를 확인하는 것이 중요하다. override var isAsynchronous: Bool {
return true
}
start
메서드를 구현하는 것이다. 작업을 수동으로 실행하든 작업 대기열이 실행 하도록 하든 상관없이, 먼저 start 메서드가 호출되고 나서 main 메서드를 호출해야 한다. override func start() {
main()
state = .executing
}
start
메서드를 오버라이드할 때는 어떤 경우에도 super를 호출하지 말아야 한다. class AsyncSumOperation: AsyncOperation {
let rhs: Int
let lhs: Int
var result: Int?
init(lhs: Int, rhs: Int) {
self.lhs = lhs
self.rhs = rhs
super.init()
}
override func main() {
DispatchQueue.global().async {
Thread.sleep(forTimeInterval: 2)
self.result = self.lhs + self.rhs
self.state = .finished
}
}
}
let queue = OperationQueue()
let pairs = [(2, 3), (5, 3), (1, 7), (12, 34), (99, 99)]
pairs.forEach { pair in
let op = AsyncSumOperation(lhs: pair.0, rhs: pair.1)
op.completionBlock = {
guard let result = op.result else { return }
print("\(pair.0) + \(pair.1) = \(result)")
}
queue.addOperation(op)
}
loadData { (data) in
loadImages (data, callback: { (images) in process Images (images, callback: { (result) in
secondaryProcessing (result, callback: { (output) in
DispatchQueue.main.async {
print("This is your processed data:")
for value in output {
print (value)
}
}
})
})
})
}
class LoadDataOperation: AsyncOperation {
var data: Data?
override func main() {
loadData { loadedData in
self.data = loadedData
self.state = .finished
}
}
}
class LoadImagesOperation: AsyncOperation {
var data: Data?
var image: [UIImage]?
override func main() {
guard let data = data else { return }
loadImages(data) { loadedImage in
self.images = loadedImages
self.state = .finished
}
}
}
let loadDataOperation = LoadDataOperation()
let loadImagesOperation = LoadImagesOperation()
let processImagesOperation = ProcessImagesOperation()
let secondaryProcessingOperation = SecondaryProcessingOperation()
loadImagesOperation.addDependency(loadDataOperation)
processImagesOpeartion.addDependency(loadImagesOperation)
secondaryProcessingOperation.addDependency(processImagesOperation)
let operations = [loadImagesOperation, processImagesOpeartion, secondaryProcessingOperation, secondaryProcessingOperation]
let queue = OperationQueue()
queue.addOperation(operations, waitUntilFinished: false)
queue.addOperation {
DispatchQueue.main.async {
print("This is your processed data:")
for value in secondaryProcessingOperation.output {
print(value)
}
}
}
import UIKit
typealias ImageOperationCompletion = ((Data?, URLResponse?, Error?) -> Void)?
final class NetworkImageOperation: AsyncOperation {
var image: UIImage?
private let url: URL
private let completion: ImageOperationCompletion
}
completion
은 URLSession
메서드에서 사용된 것과 동일하지만 선택적으로 사용된다. init(url: URL, completion: ImageOperationCompletion = nil) {
self.url = url
self.completion = completion
super.init()
}
convenience init?(string: String, completion: ImageOperationCompletion = nil) {
guard let url = URL(string: string) else { return nil }
self.init(url: url, completion: completion)
}
nil
로 설정하는 것이 타당하다. convenience init
을 선언하는 것이다. nil
을 반환한다. override func main() {
URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
guard let self = self else { return }
defer { self.state = .finished }
if let completion = self.completion {
completion(data, response, error)
return
}
guard error == nil, let data = data else { return }
self.image = UIImage(data: data)
}.resume()
}
weak
캡쳐를 해야 한다. private var urls: [URL] = []
override func viewDidLoad() {
super.viewDidLoad()
guard let plist = Bundle.main.url(forResource: "Photos", withExtension: "plist"),
let contents = try? Data(contentsOf: plist),
let serial = try? PropertyListSerialization.propertyList(from: contents, format: nil),
let serialUrls = serial as? [String] else {
print("에러")
return
}
urls = serialUrls.compactMap(URL.init)
}
compactMap
은 map
처럼 작동하며, nil
을 제외하고 unwrapped, non-optional 값만 반환한다.tableView(_: cellForRowAt:)
에서 다음 두 줄만 바꾸면 된다.let image = UIImage(name: "\(indexPath.row).png")!
let op = TiltShiftOperation(image: image)
let op = NetworkImageOperation(url: urls[indexPath.row])
// 추상 비동기 오퍼레이션의 정의==============================
class AsyncOperation: Operation {
// Enum 생성
enum State: String {
case ready, executing, finished
// KVO notifications을 위한 keyPath설정
fileprivate var keyPath: String {
return "is\(rawValue.capitalized)"
} // isReady/isExecuting/isFinished
}
// 직접 관리하기 위한 상태 변수 생성
var state = State.ready {
willSet {
willChangeValue(forKey: newValue.keyPath)
willChangeValue(forKey: state.keyPath)
}
didSet {
didChangeValue(forKey: oldValue.keyPath)
didChangeValue(forKey: state.keyPath)
}
}
}
extension AsyncOperation {
// 상태속성은 모두 read-only
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 { // 무조건 true로 리턴
return true
}
override func start() {
if isCancelled {
state = .finished
return
}
main()
state = .executing
}
override func cancel() {
super.cancel()
state = .finished
}
}
제가 학습한 내용을 요약하여 정리한 것입니다. 내용에 오류가 있을 수 있으며, 어떠한 피드백도 감사히 받겠습니다.
감사합니다.