안녕하세요! 후르륵짭짭 입니다.
Operation Queue 두번째를 이어가도록 하겠습니다.
내용을 많이 부족하지만,,, 시간이 없어서 죄송합니다.
정말 중요한 내용이 많은데, 깊게 다루지 못 했습니다.
** 블록 오퍼레이션은 서로 다른 쓰레드에서 문제가 발생해요! **
//기본적으로 의존성을 추가했기 때문에
//Operation2는 Operation1이 끝나고 수행이 된다.
func someTaskone(){
for i in 0...10{
print("someTaskOne :\(i) Thread : \(Thread.isMainThread)")
}
}
func someTaskTwo(){
for i in 0...10{
print("someTaskTwo :\(i) Thread : \(Thread.isMainThread)")
}
}
let operationQueue = OperationQueue()
let operation1 = BlockOperation(block: someTaskone)
let operation2 = BlockOperation(block: someTaskTwo)
operation2.addDependency(operation1)
operationQueue.addOperation(operation1)
operationQueue.addOperation(operation2)
위에 처럼 하면 큰 문제가 없지만
아래 처럼 할 경우 서로 다른 쓰레드에서 동작해서 문제가 발생합니다.
//위에와 같이 의존성을 추가 했지만
//다음 처럼 다른 쓰레드에서 작업하게 되면
//해당 의존성을 넣었지만, 이미 이 작업은 끝났다고 생각하게 되어
//비동기적으로 서로 작업하게 된다....
func someTaskone(){
let queue = DispatchQueue(label: "taskone" , attributes: .concurrent)
queue.async {
print("someTaskone Thread : \(Thread.current)")
for i in 0...10{
print("☺️ :\(i) Thread : \(Thread.isMainThread)")
}
}
}
func someTaskTwo(){
let queue = DispatchQueue(label: "tasktwo" , attributes: .concurrent)
queue.async {
print("someTaskTwo Thread : \(Thread.current)")
for i in 0...10{
print("🥶 :\(i) Thread : \(Thread.isMainThread)")
}
}
}
let operationQueue = OperationQueue()
let operation1 = BlockOperation(block: someTaskone)
let operation2 = BlockOperation(block: someTaskTwo)
operation2.addDependency(operation1)
operationQueue.addOperation(operation1)
operationQueue.addOperation(operation2)
** Opeation과 KVO 방식을 사용해서 이 문제를 해결하자!! **
class AsyncOperation : Operation {
enum State : String {
case ready
case executing
case finished
//KVO 방식에서 사용될 keyPath로 key 값이 된다.
var keyPath : String {
"is\(rawValue.capitalized)"
}
}
var state = State.ready {
willSet{
//KVO 방식으로 해당 값이 keyPath로 공유하게 된다.
willChangeValue(forKey: state.keyPath) //현재 값이 변경 될 예정
willChangeValue(forKey: newValue.keyPath)//새로운 값도 변경 될 예정
}
didSet{
didChangeValue(forKey: oldValue.keyPath) //이전 값이 변경 되었음
didChangeValue(forKey: state.keyPath) //현재 값이 변경 되었음
}
}
override var isFinished: Bool {
print("isFinished : \(self.state)")
return state == .finished
} //Opeation Queue에 작업이 들어가기 위해서는 isFinished가 true로 되어 있어야한다.
// 매 수행 마다 해당 값을 계속 체크한다.
override var isExecuting: Bool {
print("isExecuting : \(self.state) ")
return state == .executing
}// 매 수행 마다 해당 값을 계속 체크한다.
override var isAsynchronous: Bool{
return true
}// 매 수행 마다 해당 값을 계속 체크한다.
//비동기일 경우 : Your custom implementation must not call super at any time.
override func start() {
if isCancelled {
state = .finished
return
}
state = .executing
main()
}
override func cancel() {
print("cancel : \(self.state)")
state = .finished
}
}
class SomeTaskOneAsyncOperation : AsyncOperation {
override func main() {
let queue = DispatchQueue(label: "taskone" , attributes: .concurrent)
queue.async {
print("SomeTaskOneAsyncOperation Thread : \(Thread.current)")
for i in 0...50{
print("☺️ :\(i) Thread : \(Thread.isMainThread)")
}
self.state = .finished
}
}
}
class SomeTaskTwoAsyncOperation : AsyncOperation {
override func main() {
let queue = DispatchQueue(label: "tasktwo" , attributes: .concurrent)
queue.async {
print("SomeTaskTwoAsyncOperation Thread : \(Thread.current)")
for i in 0...50{
print("🥶 :\(i) Thread : \(Thread.isMainThread)")
}
self.state = .finished
}
}
}
위에 처럼 비동기Operation을 생성한다.
위 Operation은 KVO 방식으로 작동하고
Operation은 해당 작업이 Queue에서 작업해야할 건지 아닌지
isReady , isExecuting, isFinished, isCanceled를 통해서 판단하게 된다.
그런데 read Only이기 때문에 위에 처럼 우리가 직업 overrid 받아서 사용해줘야한다.
//비동기적 Opeartion Queue를 생성하는 방법
let operationQueue = OperationQueue()
let operation1 = SomeTaskOneAsyncOperation()
let operation2 = SomeTaskTwoAsyncOperation()
operation1.completionBlock = {print("SomeTaskOneAsyncOperation completed")}
operation2.completionBlock = {print("SomeTaskTwoAsyncOperation completed")}
operation2.addDependency(operation1) //KVO 방식을 사용한 상태에서 의존성을 추가하면 같은 Thread 주소를 가지게 된다.
operationQueue.addOperation(operation1)
operationQueue.addOperation(operation2)
** 위의 비동기 Operation을 Thread Safe 하게 만드는 방법 **
//의존성 없이 비동기적 작업을 할때 Thread Safe하게 할 수 있는 방법
class AsyncOperation2 : Operation {
enum State : String {
case ready
case executing
case finished
var keyPath : String {
"is\(rawValue.capitalized)"
}
}
private let stateQueue = DispatchQueue(label: "custon", attributes: .concurrent)
private var _state = State.ready
var state : State {
get{
stateQueue.sync {
return _state
}
}
set{
let oldValue = state
willChangeValue(forKey: state.keyPath)
willChangeValue(forKey: newValue.keyPath)
stateQueue.sync(flags: .barrier) {
_state = newValue
}
didChangeValue(forKey: state.keyPath)
didChangeValue(forKey: oldValue.keyPath)
}
}
override var isFinished: Bool {
print("isFinished : \(self.state)")
return state == .finished
} //Opeation Queue에 작업이 들어가기 위해서는 isFinished가 true로 되어 있어야한다.
override var isExecuting: Bool {
print("isExecuting : \(self.state) ")
return state == .executing
}
override var isAsynchronous: Bool{
// print("isAsynchronous : \(self.state)")
return true
}
//비동기일 경우 : Your custom implementation must not call super at any time.
override func start() {
if isCancelled {
state = .finished
return
}
state = .executing
main()
}
override func cancel() {
print("cancel : \(self.state)")
state = .finished
}
}
class SomeTaskOneAsyncOperation2 : AsyncOperation {
override func main() {
let queue = DispatchQueue(label: "taskone" , attributes: .concurrent)
queue.async {
print("SomeTaskOneAsyncOperation2 Thread : \(Thread.current)")
for i in 0...50{
print("☺️ :\(i) Thread : \(Thread.isMainThread)")
}
self.state = .finished
}
}
}
class SomeTaskTwoAsyncOperation2 : AsyncOperation {
override func main() {
let queue = DispatchQueue(label: "tasktwo" , attributes: .concurrent)
queue.async {
print("SomeTaskTwoAsyncOperation2 Thread : \(Thread.current)")
for i in 0...50{
print("🥶 :\(i) Thread : \(Thread.isMainThread)")
}
self.state = .finished
}
}
}
추후 다룰내용이지만
큐를 만들고 해당 값을 수정할 때만 barrier를 만들어서
Thread Safe하게 만든다.
let operationQueue = OperationQueue()
let operation1 = SomeTaskOneAsyncOperation2()
let operation2 = SomeTaskTwoAsyncOperation2()
operation1.completionBlock = {print("SomeTaskOneAsyncOperation2 completed")}
operation2.completionBlock = {print("SomeTaskTwoAsyncOperation2 completed")}
operationQueue.addOperation(operation1)
operationQueue.addOperation(operation2)
참고 사이트 :
https://ali-akhtar.medium.com/concurrency-in-swift-custom-operations-part-4-154b60bff84c
https://blog.bitbebop.com/asynchronous-operations-swift/
https://ali-akhtar.medium.com/concurrency-in-swift-grand-central-dispatch-part-1-945ff05e8863
https://ali-akhtar.medium.com/concurrency-in-swift-grand-central-dispatch-part-2-1b0b025ee381
'Xcode > Swift - PlayGround' 카테고리의 다른 글
PlayGround) Serial에서 Async는 머지? (0) | 2022.02.28 |
---|---|
PlayGround) RxSwift-Error Handle (0) | 2022.02.07 |
PlayGround) Opeation Queue - 1 (0) | 2022.01.17 |
PlayGround) URLSessionDownload를 이용해서 PDF 파일 다운로드 (0) | 2021.12.26 |
PlayGround)Enum의 활용 (0) | 2021.09.22 |
댓글