๋™์‹œ์„ฑ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ์œ„ํ•ด apple์—์„œ๋Š” GCD๋ผ๋Š” ๊ฒƒ์„ ์ œ๊ณตํ•œ๋‹ค๋Š” ์‚ฌ์‹ค์„ ์ด์ œ ์šฐ๋ฆฌ๋Š” ์•Œ๊ณ  ์žˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ, ์ด์™ธ์— ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์ด ํ•˜๋‚˜ ๋”์žˆ๋‹ค. ๋ฐ”๋กœ Operation Queue์ด๋‹ค. ์กฐ๊ธˆ๋” ๊ณ ์ˆ˜์ค€์˜ API๋กœ ์„ค๊ณ„๋œ Operation Queue๋Š” ๋ณต์žกํ•œ ์ž‘์—…๋“ค์„ ์ˆ˜ํ–‰ํ•  ๋•Œ ๋งŽ์ด ์‚ฌ์šฉํ•˜๊ณค ํ•œ๋‹ค. ๊ทธ๋Ÿผ, ๊ตฌ์ฒด์ ์œผ๋กœ ์–ด๋–ค ์žฅ์ ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•ด๋ณด๋„๋ก ํ•˜์ž.

ํ•ด๋‹น ๊ธ€์€ ์‚ฌ๋‚ด ๋ฐœํ‘œ์™€ ์ถ”๊ฐ€ ์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ž‘์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค.

Operation Queue

GCD๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ตฌํ˜„๋œ Objective-c class๋กœ, ๊ณ ์ˆ˜์ค€์˜ DispatchQueue์ด๋‹ค.

GCD์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ Thread ์ง์ ‘ ์ƒ์„ฑ ๋Œ€์‹ , ํŠน์ • ์ž‘์—…์„ ์ •์˜ํ•˜๊ณ  Queue์— ์ œ์ถœํ•˜์—ฌ ์‹œ์Šคํ…œ์ด ์ด๋ฅผ ์ˆ˜ํ–‰ํ•˜๋„๋ก ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•œ๋‹ค. Operation์ด๋ผ๋Š” ํด๋ž˜์Šค๋ฅผ ํ†ตํ•ด ์ˆ˜ํ–‰ํ•  ์ž‘์—…์„ ์ •์˜ํ•˜๊ณ , OperationQueue์— ์ œ์ถœํ•˜๋ฉด, Operation ๊ฐ์ฒด์˜ ์šฐ์„  ์ˆœ์œ„ ๋ฐ ์ค€๋น„ ์ƒํƒœ์— ๋”ฐ๋ผ ๋Œ€๊ธฐ์ค‘์ธ Operation์„ ์‹คํ–‰ํ•œ๋‹ค. ์ƒ๋‹นํžˆ DispatchQueue์™€ ์œ ์‚ฌํ•จ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

DispatchQueue์™€ ๋‹ค๋ฅธ ์ ์€, ๋™์‹œ์— ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” Operation์˜ ์ตœ๋Œ€ ๊ฐฏ์ˆ˜๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ด๋‹ค. ๋˜ํ•œ ์•„์ง ์‹คํ–‰์ด ๋˜์ง€ ์•Š์€ Operation์„ ๋งค์šฐ ์‰ฝ๊ฒŒ ์ทจ์†Œํ•  ์ˆ˜ ์žˆ๋‹ค. GCD์˜ ๊ฒฝ์šฐ, DispatchWorkItem์—์„œ cancel ๋ฉ”์„œ๋“œ๋ฅผ ํ™œ์šฉํ–ˆ์—ˆ๋‹ค. ์ฆ‰ ํ•ด๋‹น Item์—์„œ ์ ‘๊ทผํ–ˆ์–ด์•ผ ํ–ˆ๋Š”๋ฐ, Operation Queue์˜ ๊ฒฝ์šฐ queue.cancelAllOperations() ๋ผ๋Š” ๋ฉ”์„œ๋“œ๋กœ ํ•œ๋ฒˆ์— ๊ฐ€๋Šฅํ•˜๋‹ค.

Operation Queue ์ƒ์„ฑ ๋ฐฉ๋ฒ•์€ GCD์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ 2๊ฐ€์ง€๊ฐ€ ์žˆ๋‹ค.

  • Main Queue

    • OperationQueue.main
    • main thread ์‹คํ–‰
  • Concurrent Queue (Default)

    let queue = OperationQueue()
    queue.maxConvurrentoperationCount = OperationQueue.defaultMaxConcurrentOperationCount
    • maxConvurrentoperationCount ๋ณ€์ˆ˜์— ์ˆซ์ž๋ฅผ ๋„ฃ์œผ๋ฉด ํ•œ๋ฒˆ์— n๊ฐœ์”ฉ ๋น ์ ธ๋‚˜๊ฐ€๊ฒŒ ๋œ๋‹ค.
    • maxConvurrentoperationCount๋ฅผ 1๋กœ ์„ค์ •ํ•˜๋ฉด serial Queue์˜ ๋™์ž‘์„ ํ•˜๊ฒŒ๋œ๋‹ค.

Operation ์ถ”๊ฐ€์˜ ๊ฒจ์›… ์•„๋ž˜์™€ ๊ฐ™์€ API๊ฐ€ ์žˆ๋‹ค.

let queue = OperationQueue()
queue.addOperation(op: Operation)
queue.addOperation(block: () -> Void)
queue.addOperations(ops: [Operation], waitUntilFinished: Bool)

KVO-Compliant Properties

OperationQueue ํด๋ž˜์Šค๋Š” ์—ฌ๋Ÿฌ Property์— ๋Œ€ํ•ด Key-value Coding ๋ฐ Key-value Observing์„ ์ค€์ˆ˜ํ•œ๋‹ค. KVO๋Š” ์ดํ›„ ์•Œ์•„๋ณด๊ณ , ์ผ๋‹จ ๊ฐ€๋Šฅํ•œ property๋ฅผ ๋ณด๋ฉด

  • operations: Deprecated
    • ๋“ค์–ด๊ฐ€ ์žˆ๋Š” Operation๋“ค
  • operationCount: Deprecated
    • Operations์˜ ๊ฐœ์ˆ˜
  • maxConcurrentOperationCount: readable, writeable
    • ๋™์‹œ์— ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ์ตœ๋Œ€ ์ž‘์—…์ˆ˜
  • isSuspended: readable, writeable
    • Queue์˜ ์ผ์‹œ ์ค‘๋‹จ ์—ฌ๋ถ€
  • name: readable, writable
    • ํ•ด๋‹น Queue์˜ ์ด๋ฆ„
let queue = OperationQueue()
 
let observation = queue.observe(\.isSuspended, changeHandler: { operation, _ in
    print("isSuspended: \(operation.isSuspended)")
})
 
queue.isSuspended = true

OperationQueue์˜ issuspended Property๋ฅผ ๊ด€์ฐฐํ•  ์ˆ˜ ์žˆ๋‹ค.

Key-Value Coding (KVC)

๋ฌธ์ž์—ด ์‹๋ณ„์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ์ฒด์˜ ํ”„๋กœํผํ‹ฐ์— ๊ฐ„์ ‘์ ์œผ๋กœ ์•ก์„ธ์Šคํ•˜๊ธฐ ์œ„ํ•œ ๋ฉ”์ปค๋‹ˆ์ฆ˜

objective runtime์— ์˜์กดํ•˜๊ธฐ ๋•Œ๋ฌธ์— NSObject ์ƒ์†๊ณผ, @objc annotation์„ ์ ์–ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

class KVC: NSObject {
    @objc var value: Int
 
    override init() {
        value = 999
        
        super.init()
    }
}
 
let kvc = KVC()
print(kvc.value(forKey: "value")) // Optional(999)
 
kvc.setValue(5, forKey: "value") // setting value for Optional(5)
 
// Swift 4 KeyPath
print(kvc[KeyPath: \KVC.value]) // 5
 

๋ฌธ์ž์—ด๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์—, ์•Œ๊ฒ ์ง€๋งŒ ๋Ÿฐํƒ€์ž„์œผ๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค. ๊ทธ๋ž˜์„œ ์ƒ๋‹นํžˆ ์œ ์—ฐํ•˜๋‹ค. ๋ฐ˜๋Œ€๋กœ ๋งํ•˜๋ฉด ์˜ค๋ฅ˜๊ฐ€ ๋‚  ๊ฐ€๋Šฅ์„ฑ๋„ ๋†’๋‹ค.

Key-Value Observing (KVO)

๋‹ค๋ฅธ ๊ฐ์ฒด์˜ ํŠน์ • ํ”„๋กœํผํ‹ฐ์— ๋Œ€ํ•œ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์•Œ๋ฆผ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ๋ฉ”์ปค๋‹ˆ์ฆ˜

KVC๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ objective-c runtime์—์„œ ๊ฐ„์ ‘์ ์œผ๋กœ ํŠน์ • property์— ์ ‘๊ทผํ•˜๊ธฐ ๋•Œ๋ฌธ์—, NSObject ์ƒ์†๊ณผ, @objc, dynamic annotation์„ ๋ถ™์—ฌ์„œ ๋Ÿฐํƒ€์ž„์— ๋…ธ์ถœ ์‹œ์ผœ์•ผ ํ•œ๋‹ค.

class KVO: NSObject {44
    @objc dynamic var value = 0
}
 
let kvo = KVO()
 
let observation = kvo.observe(\.value, changeHandler: { kvo, _ in
    print(kvo.value)
})
 
kvo.value = 99 // ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜์—ˆ์„ ๋•Œ, ์ถœ๋ ฅ๋จ

observe ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด์„œ ๋ณ€๊ฒฝ์„ ์•Œ๋ฆผ ๋ฐ›๊ณ  ์‹ถ์€ ๋ณ€์ˆ˜๋ฅผ ๋ช…์‹œํ•˜๊ณ , ์ฝœ๋ฐฑ์œผ๋กœ ์ •์˜ํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

Operation

์ˆ˜ํ–‰ํ•˜๋ ค๋Š” ์ž‘์—…์„ ์บก์Šํ™”ํ•˜๋Š” Objective-C ๊ธฐ๋ฐ˜ ์ถ”์ƒ ํด๋ž˜์Šค

์ถ”์ƒ ํด๋ž˜์Šค์ด๊ธฐ ๋•Œ๋ฌธ์— ์ง์ ‘ ์‚ฌ์šฉํ•˜์ง€ ๋ง๊ณ  Subclass๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜, ์‹œ์Šคํ…œ ์ •์˜ Subclass ์ค‘ ํ•˜๋‚˜๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

  • BlockOperation
  • Subclassing

OperationQueue์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜, ๋‹จ๋…์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋‹จ๋…์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ sync(default), async๋กœ ์‹คํ–‰๋˜๋„๋ก ์ž‘์—… ์„ค๊ณ„๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.

KVO-Compliant Properties

Operation ํด๋ž˜์Šค๋Š” ์—ฌ๋Ÿฌ Property์— ๋Œ€ํ•ด KVC ๋ฐ KVO๋ฅผ ์ค€์ˆ˜ํ•œ๋‹ค.

  • isCancelled: read-only
  • isAsynchronous: read-only
  • isExecuting: read-only
  • isFinished: read-only
  • isReady: read-only
  • dependencies: read-only
  • queuePriority: readable, writable
  • completionBlock: readable, writable
let operation = BlockOperation(block: {
    print("task")
})
 
let observation1 = operation.observe(\.isExecuting, changeHandler: { operation, _ in
    print("isExecuting: \(operation.isExecuting)")
})
 
operation.start()

Operation Life Cycle

  • pending
  • Ready
  • Executing
  • Finished
  • Cancelled
    • Bool Property
let operation = BlockOperation(block: {
    print("task")
})
 
let observation = operation.observe(\.isCancelled, changeHandler: { operation, _ in 
    print("isCancelled: \(operation.isCancelled)")
})
 
operation.cancel() // isCancelled: true
operation.start()

cancel ์ดํ›„์—๋Š” startํ•˜๋”๋ผ๋„ ๊ฐ’์ด ์‹คํ–‰๋˜์ง€ ์•Š๋Š”๋‹ค.

Block Operation

ํ•˜๋‚˜ ์ด์ƒ์˜ ๋ธ”๋ก ๊ฐ์ฒด์— ๋Œ€ํ•œ Wrapper ์—ญํ• ์„ ํ•˜๋Š” Operation์˜ Subclass

let operation1 = BlockOperation {
    print("task1")
}
 
let operation2 = BlockOperation {
    print("task2")
}
 
let operation3 = BlockOperation {
    print("task3")
}
 
operation1.start()
operation2.start()
operation3.start()

Operation์„ ๋‹จ๋…์œผ๋กœ ์“ฐ๋Š” ๊ฒฝ์šฐ ๊ธฐ๋ณธ์ ์œผ๋กœ sync๋กœ ๋Œ์•„๊ฐ„๋‹ค๊ณ  ํ–ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฏ€๋กœ ์ˆœ์ฐจ์ ์œผ๋กœ 1, 2, 3์œผ๋กœ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜จ๋‹ค. ์ฆ‰, ๊ฐ๊ฐ์˜ ์ œ์–ด๊ถŒ์„ ๋‚ด๋†“์ง€ ์•Š๋Š”๋‹ค.

let operation = BlockOperation { 
    for _ in 0..<100 {
        print("task 1")
    }
}
 
operation.addExecutionBlock {
    for _ in 0..<100 {
        print("task 2")
    }
}
 
operation.start()

addExecutionBlock ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ๋” ๋งŽ์€ ๋ธ”๋Ÿญ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค. ์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ๊ฒƒ์€ ์ถ”๊ฐ€๋œ ๊ฐ block๋“ค์€ Concurrentํ•˜๊ฒŒ ๋™์ž‘ํ•œ๋‹ค. ์ฆ‰, ํ•ด๋‹น ์˜ˆ์ œ์˜ ๊ฒฐ๊ณผ๋Š” 1, 2์˜ ์ˆœ์„œ ๋ณด์žฅ์ด ์•ˆ๋œ๋‹ค.

Custom Operation

Operation ํด๋ž˜์Šค์˜ ํ•˜์œ„ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด ์‹คํ–‰ ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ์ปค์Šคํ…€ ๋กœ์ง์„ ์ œ๊ณต

Non-Concurrent Operation

Operation์„ ์ƒ์†ํ•˜๊ณ , main method ํ•˜๋‚˜๋งŒ์„ overrideํ•˜๋ฉด ๋œ๋‹ค.

class NonConcurrentOperation: Operation {
    override func main() {
        super.main()
        // task๋ฅผ ์œ„ํ•œ code ์ž‘์„ฑ
    }
}
 
let operation = NonConcurrentOperation()
operation.start()

Concurrent Operation

Operation์„ ๋จผ์ € ๋น„๋™๊ธฐ๋กœ ์ž‘๋™ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“ค์–ด ์ฃผ์–ด์•ผ ํ•œ๋‹ค.

  • isAsynchronous
  • isExecuting
  • isFinished
  • start()

์œ„ 4๊ฐœ์˜ ํ”„๋กœํผํ‹ฐ ํ˜น์€ ๋ฉ”์„œ๋“œ๋ฅผ overrideํ•ด์•ผ ํ•œ๋‹ค. ์œ„์˜ 3๊ฐœ์˜ property์˜ ๊ฒฝ์šฐ KVO๋ฅผ ์ง€์›ํ•˜๋Š”๋ฐ, ๊ฐ’์ด ๋ณ€ํ™”ํ•  ๋•Œ ์ด๋ฅผ ์•Œ๋ ค์ฃผ๋Š” notification์„ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค.

class AsyncOperation: Operation {
    override var isAsynchronous: Bool { // ๋จผ์ € concurrentํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด ๋น„๋™๊ธฐ๋ฅผ ์ผœ์ค€๋‹ค.
        return true
    }
 
    private var _isExecuting = false
    override var isExecuting: Bool {  // property override์‹œ getter, setter ์ž‘์„ฑ ํ•„์š”
        get {
            return _isExecuting
        }
 
        set {
            willChangeValue(for: \.isExecuting)
            _isExecuting = newValue
            didChangeValue(for: \.isExecuting)
        }
    }
 
    private var _isFinished = false
    override var isFinished: Bool {  
        get {
            return _isFinished
        }
 
        set {
            willChangeValue(for: \.isFinished)
            _isFinished = newValue
            didChangeValue(for: \.isFinished)
        }
    }
 
    override func start() {
        if self.isCancelled {
            self.isFinished = true
            return
        }
 
        self.isExecuting = true
        self.main()
    }
 
    func completeOperation() {
        self.isExecuting = false
        self.isFinished = true
    }
}

property override์‹œ getter, setter๋ฅผ ๊ฐ€์ง€๊ณ  ์ €์žฅ ํ”„๋กœํผํ‹ฐ์˜ ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๊ณ , ๊ฐ€์ ธ์™€์•ผ ํ•œ๋‹ค. ๊ทธ๋ž˜์„œ ์ €๋ ‡๊ฒŒ ์ž‘์„ฑํ•œ ๊ฒƒ. ์ด๋ ‡๊ฒŒ ๋งŒ๋“ค์–ด๋‘๊ณ  ์‹ค์ œ ์‚ฌ์šฉ์€ ์–ด๋–ป๊ฒŒ ํ• ๊นŒ?

class SomeAsyncOperation:n AsyncOperation {
    override func main() {
        super.main()
        // ๋น„๋™๊ธฐ ์ž‘์—…์„ ์ •์˜ํ•œ๋‹ค.
        AsyncTask {
            // some code
            self.completeOperation() // capture ์กฐ์‹ฌ
        }
    }
}
 
let operation = SomeAsyncOperation()
operation.start()

start ํ•จ์ˆ˜ ์•ˆ์— main ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•˜๋„๋ก ๋˜์–ด ์žˆ์œผ๋‹ˆ, ์šฐ๋ฆฌ๊ฐ€ ์‚ฌ์šฉํ•  ์ฝ”๋“œ๋Š” main ํ•จ์ˆ˜๋ฅผ overrideํ•˜์—ฌ ์ •์˜ํ•ด์ฃผ๊ธฐ๋งŒ ํ•˜๋ฉด ๋œ๋‹ค.

Operation Dependency

ํŠน์ • ์ˆœ์„œ๋กœ Operation์„ ์‹คํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•

Operation A โ† Operation B

operation B๊ฐ€ operation A์— ์˜์กด์„ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๊ณ  ์„ค์ •ํ•ด๋ณด์ž.

let operationA = ...
let operationB = ...
 
operationB.addDependency(operationA)

์œ ์˜ํ•ด์•ผ ํ•˜๋Š” ์ ์€ ์ข…์†(A) Operation์ด ์„ฑ๊ณต์ ์œผ๋กœ ์™„๋ฃŒ๋˜์—ˆ๋Š”์ง€, ํ˜น์€ ์‹คํŒจ๋˜์—ˆ๋Š”์ง€ ๊ตฌ๋ถ„ํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์ ์ด๋‹ค. ์ฆ‰, A์˜ ์ž‘์—…์ด ์„ฑ๊ณต์ธ์ง€ ์‹คํŒจ์ธ์ง€์— ๊ด€๊ณ„ ์—†์ด B๊ฐ€ ์‹คํ–‰๋œ๋‹ค. ๋˜ ์ทจ์†Œํ•œ ๊ฒฝ์šฐ๋„ ์™„๋ฃŒ๋กœ ํ‘œ์‹œ๋œ๋‹ค.

๋งŒ์•ฝ์— ์ข…์† Operation์ด ์ทจ์†Œ๋˜๊ฑฐ๋‚˜ ํ˜น์€ ์‹คํŒจํ•œ ๊ฒฝ์šฐ ํ›„์† Operation์˜ ๋™์ž‘์„ ์ •ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ๋ณ„๋„๋กœ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

RandomNumber๋ฅผ ๋งŒ๋“ค๊ณ , ํ•ด๋‹น ๊ฐ’์„ ๋‹ค์Œ Operation์— ๋„˜๊ฒจ Printํ•˜๋„๋ก ํ•˜๋Š” ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž.

class RandomNumberOperation: Operation {
    var value: Int?
 
    override func main() {
        super.main()
 
        self.value = (0..<100).randomElement()
    }
}
 
class PrintOperation: Operation {
    var value: Int?
 
    override func main() {
        super.main()
 
        guard let value = self.vaule else {
            return
        }
 
        print(self.value)
    }
}
 
let randomNumberOperation = RandomNumberOperation()
let printOperation = PrintOperation()

RandomNumberOperation์€ ๊ฐ’์„ ๋งŒ๋“ค๊ณ , PrintOperation์€ ๊ฐ’์ด ์žˆ๋Š” ๊ฒฝ์šฐ ์ถœ๋ ฅํ•˜๋„๋ก ๋˜์–ด ์žˆ๋‹ค. ์–ด๋–ป๊ฒŒ ์—ฐ๊ฒฐํ•˜๊ณ  ๊ฐ’์„ ๋„˜๊ธธ ์ˆ˜ ์žˆ์„๊นŒ?

Completion Block ์‚ฌ์šฉ

randomNumberOperation.completionBlock = { [unowned randomNumberOperation, unowned printOperation] in
    printOperation.value = randomNumberOperation.value
}
 
printOperation.addDependency(randomNumberOperation)
 
let queue = OperationQueue()
queue.addOperations([randomNumberOperation, printOperation], waitUntilFinished: true)
  1. Completion Block์„ ์ž‘์„ฑํ•œ๋‹ค.
    • ์ด ๊ฒฝ์šฐ ์™œ unowned๋ฅผ ์‚ฌ์šฉํ•˜์˜€์„๊นŒ..?
  2. ์ž‘์„ฑํ•˜์—ฌ ์ข…์† Operation์˜ property๋กœ ๋„ฃ์–ด๋‘”๋‹ค.
  3. ์˜์กด์„ฑ ๊ด€๊ณ„๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.
  4. ํ์— ์ˆœ์„œ๋Œ€๋กœ ์ž‘์—…์„ ๋„ฃ๋Š”๋‹ค.

Adapter Operation ์‚ฌ์šฉ

let adapter = BlockOperation { [ unowned randomNumberOperation, unowned printOperation] in
    printOperation.value = randomNumberOperation.value
}
 
adapter.addDependency(randomNumberOperation)
printOperation.addDependency(dapter)
 
let queue = OperationQueue()
queue.addOperations([randomNumberoperation, adapter, printOperation], waitUntilFinished: true)

adapter pattern์€ ๋ง๊ทธ๋ž˜๋„ ๋‘ ๊ฐ์ฒด๋ฅผ ์—ฐ๊ฒฐํ•  ๋•Œ, ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด์„œ ์—ฐ๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค. Operation์„ ํ•˜๋‚˜ ๋งŒ๋“ค๊ณ , ์ด ์‚ฌ์ด์—์„œ ์„œ๋กœ์˜ ์˜์กด์„ฑ์„ ์—ฐ๊ฒฐํ•œ๋‹ค.

randomNumberOperation <- adapter <- printOperation

์ด ๋ฐฉ๋ฒ• ์—ญ์‹œ ์ˆœํ™˜์ฐธ์กฐ๋ฅผ ํ•ญ์ƒ ์กฐ์‹ฌํ•ด์•ผ ํ•œ๋‹ค.

Operation Priority

Operation Queue ๋‚ด์— ํ˜„์žฌ ์ค€๋น„๋œ Operation์˜ ์‹คํ–‰ ์‹œ์ž‘ ์ˆœ์„œ๋ฅผ ์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

ํ์— ์ถ”๊ฐ€๋œ ์ž‘์—…์˜ ๊ฒฝ์šฐ, ๋จผ์ € operation์˜ ์ค€๋น„ ์ƒํƒœ์— ๋”ฐ๋ผ ์‹คํ–‰ ์ˆœ์„œ๊ฐ€ ๊ฒฐ์ •๋œ ๋‹ค์Œ ์ƒ๋Œ€์  ์šฐ์„  ์ˆœ์œ„์— ๋”ฐ๋ผ ๊ฒฐ์ •๋œ๋‹ค.

์—ฌ๊ธฐ์„œ ์•Œ์•„๋‘์–ด์•ผ ํ•  ์ ์€, ์šฐ์„  ์ˆœ์œ„๊ฐ€ ์ข…์†์„ฑ์„ ๋Œ€์ฒดํ•˜์ง€๋Š” ์•Š๋Š”๋‹ค๋Š” ์ ์ด๋‹ค. ์šฐ์„ ์ˆœ์œ„ ๊ฐ’์„ ์‚ฌ์šฉํ•ด์„œ ์„œ๋กœ๋‹ค๋ฅธ Operation์˜ ์ˆœ์„œ๋ฅผ ๊ด€๋ฆฌํ•˜๋ฉด ์•ˆ๋œ๋‹ค. ์ข…์†์„ฑ์ด ์—†๋Š” Operation์— ๋Œ€ํ•ด์„œ๋งŒ ์ƒ๋Œ€์  ์šฐ์„  ์ˆœ์œ„ ๋ถ„๋ฅ˜๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ, ํ•ด๋‹น ๊ฐ’์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

public enum QueuePrioirity: Int {
    case veryLow = -8
    case low = -4
    case normal = 0
    case high = 4
    case veryhigh = 8
}
 
let operationA = BlockOperation {
    print("operatoinA")
}
 
let operationB = BlockOperation {
    print("operationB)
}
 
let operationC = BlockOperation {
    print("operationC)
}
 
operationA.queuePrioirity = .veryLow
operationB.queuePrioirity = .normal
operationC.queuePrioirity = .high
 
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
queue.addOperations([operationA, operationB, operationC], waitUntilFinished: true)

C, B, A ์ˆœ์œผ๋กœ Operation์ด ์‹คํ–‰๋œ๋‹ค. Serial Queue์ด๊ธฐ ๋•Œ๋ฌธ์— ์ผ๊ด€๋œ ๊ฒฐ๊ณผ๊ฐ€ ๋„์ถœ๋œ๋‹ค.

Quality of Service

apple์ด ์ œ๊ณตํ•˜๋Š” ์ž‘์—…์˜ ๋ช…์‹œ์ ์ธ ๋ถ„๋ฅ˜

์ด์ „์˜ GCD์—์„œ๋„ ์ œ๊ณต๋˜์—ˆ๋˜ ๊ฐœ๋…์ด๋‹ค.

  • User Interactive
  • User Initiated
  • Utility
  • Background
let operation = BlockOperation {
    print("Long Running task")
}
 
operation.qualityOfService = .utility
 
let queue = OperationQueue()
queue.addOperation(operation)

์ด๋Ÿฐ์‹์œผ๋กœ operation์˜ property๋กœ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. ํ˜น์€

let operation = BlockOperation {
    print("Long Running Task")
}
 
let queue = OperationQueue()
queue.qualityOfService = .utility
queue.addOperation(operation)

์ด๋ ‡๊ฒŒ queue ์ž์ฒด์— ์ง€์ •ํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ์ฆ‰ operation ์ž์ฒด์— ์ง€์ •ํ•˜๋ฉด OS์—์„œ ์ง€์ •ํ•œ Queue๋กœ ๋“ค์–ด๊ฐ€๊ณ , Queue์— ์„ค์ •ํ•˜๋ฉด ๊ทธ๋Ÿฌํ•œ ํ๊ฐ€ ์ƒ์„ฑ๋˜์–ด ๋™์ž‘ํ•˜๊ฒŒ ๋˜๋Š” ๊ฒƒ.

๊ทธ๋Ÿฐ๋ฐ ๋งŒ์•ฝ์—, Queue์— ์ง€์ •๋œ QoS๋ณด๋‹ค ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ Operation์ด ๋“ค์–ด๊ฐ€๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ?

์ƒํ™ฉ๊ฒฐ๊ณผ
Queue์— ํ• ๋‹น๋œ Qo
Queue QoS ํ• ๋‹น ์—ฌ๋ถ€ ๋ฐ ์ƒ๋Œ€์  ์šฐ์œ„Operation QoS ํ• ๋‹น ์—ฌ๋ถ€ ๋ฐ ์ƒ๋Œ€์  ์šฐ์œ„๊ฒฐ๊ณผ
XO์•„๋ฌด ์˜ํ–ฅ ์—†์Œ.
Operation์— ์ง€์ •๋œ QoS๋กœ ๋™์ž‘
O (Low QoS)O (High QoS)Queue์˜ QoS ์Šน๊ฒฉ
Queue ๋‚ด๋ถ€ Operation QoS ์—ญ์‹œ ์Šน๊ฒฉ
O (Low โ†’ High๋กœ ๋ณ€๊ฒฝ)- (ํ• ๋‹น ๋˜์—ˆ์„ ์ˆ˜๋„ ์•ˆ๋˜์—ˆ์„ ์ˆ˜๋„)QoS๊ฐ€ ๋‚ฎ์€ Queue ๋‚ด๋ถ€ Operation์€ ๋ชจ๋‘ ์Šน๊ฒฉ
O (High โ†’ Low๋กœ ๋ณ€๊ฒฝ)-๊ธฐ์กด์˜ Queue ๋‚ด๋ถ€ Operation์€ ๋ฐ˜์˜๋ฐ›์ง€ ์•Š์Œ
์ƒˆ๋กœ ์ถ”๊ฐ€๋˜๋Š” Operation์—๋งŒ ๋ฐ˜์˜

GCD vs OperationQueue

์ž‘์—…์˜ ๋ณต์žก๋„์— ๋”ฐ๋ผ์„œ ์„ ํƒ

  • ๋‹จ์ˆœํ•œ block์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒฝ์šฐ: DispatchQueue
  • ์ž‘์—…์˜ ์ข…์†์„ฑ, ์ทจ์†Œ ๋นˆ๋ฒˆ: Operation, OperationQueue

์ •๋ฆฌ

  • OperationQueue ์—ญ์‹œ Serial, Concurrent๊ฐ€ ์žˆ๋‹ค.
  • maxConcurrentOperationCount๋ฅผ ํ†ตํ•ด ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค.
  • Operation์€ Block Operation์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ Operation์„ Subclassingํ•˜์—ฌ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
  • Operation Life Cycle์ด ์žˆ๊ณ , ์ทจ์†Œ, ์‹คํ–‰์ค‘ ๋“ฑ์˜ ์ƒํƒœ๊ฐ€ ์žˆ๋‹ค.
  • KVO, KVC๋ฅผ ์ง€์›ํ•œ๋‹ค. (OperationQueue, Operation)
  • Operation Dependency๋ฅผ ๊ฐ€์ ธ์„œ, Operation๊ฐ„์˜ ์ข…์†์„ฑ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์šฐ์„ ์ˆœ์œ„ ๋˜ํ•œ ์„ค์ •๊ฐ€๋Šฅํ•˜๋ฉฐ, ๋‹ค๋งŒ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ์ข…์†์„ฑ์„ ๋Œ€์ฒดํ•˜์ง€๋Š” ์•Š๋Š”๋‹ค.
  • DispatchQueue์™€ ๊ฐ™์ด QoS๋ฅผ ์ง€์›ํ•œ๋‹ค.

Reference