본문 바로가기

IOS

DispatchworkItem

쓰고 있는 글들을 계속해서 써야하는데 오늘 회사에서 일을 하다가 갑자기 오! 이거 글로 한번 써봐도 괜찮겠다 싶어서 예전의 저와 같이 고생하고 있거나 고생 예정인 분들 또는 고생했던 분들에게 힌트가 될 수 있길 바라며 글을 시작해보겠습니다.

 

 

개념

1. 정의

DispatchworkItem은 공식문서에는 위와 같이 정의되어 있습니다. 

 

'perform하기를 원하는 작업은 completion handle 또는 execution dependencies을 attach할 수 있는 방식으로 캡슐화된다'는데 사실 추측은 하나 무슨 말인지 잘 와닿지 않았습니다.

 

오히려 OverView의 내용을 보니 명확해졌습니다.

네. 첫 문장에 나와있듯이 DispatchWorkItem은 DispatchQueue 또는 DispatchGroup 내에서 수행할 작업을 캡슐화할 때 사용한다고 합니다. 두 번째 문장에 나와있듯이 work item을 DispatchSourece event, 등록, 취소 handler로도 사용할 수 있다고 하네요.

 

오늘 글은 여기서 첫 문장의 내용에 대해서 다뤄보고자 합니다.

 

2. 필요한 이유

정의에 나와있듯이 수행할 작업을 캡슐화할 때 사용한다면, 캡슐화를 하지 않고 아래와 같이 사용해서 code가 실행되도록 한다면 뭐가 다를까요? 

DispatchQueue.main.async {
	//code
}

 

또는 아래처럼 code가 실행되게 한다면 뭐가 다를까요?

let customQueue = DispatchQueue(label: "customQueue")

customQueue.async {
	//code
}
더보기

여기서 다루고자 하는 내용은 Queue가 Serial한지 Concurrent한지를 다루고자 하는 내용이 아니니 이와 관련한 내용은 GCD관련 내용에서 따로 한번 다뤄보도록 하겠습니다.

 

그리고 DispatchWorkItem을 통해서 아래처럼 code를 실행한다면 또 뭐가 다를까요?

let workItem = DispatchWorkItem {
    //code
}

DispatchQueue.main.async(execute: workItem)
customQueue.async(execute: workItem)

 

굳이 따지자면 main queue냐 custom queue냐 정도일까요?

좀더 들어가보더라도 UI관련 코드는 main queue에서 실행되게 해야하니  그것만 잘 지켜주면된다 정도일까요?

 

뭐가 다른 걸까요?!

 

바로 '취소'가 가능하다는 것입니다.

 

Work Item을 취소하는 것과 관련한 메서드와 프로퍼티는 위와 같습니다.

 

3. 필요 상황

저는 아래와 같이 특정 뷰 아래에 있는 뷰의 isHidden 상태에 따라 isHidden을 toggle해줘야하는 상황 + isHidden이 false일 때 몇 초 뒤 isHidden이 true가 되도록 toggle하는 기능이 필요했습니다.


아래 gif는 계속 탭 탭 탭을 반복하는 상황인데 잘 보시면 중간 중간 정상적으로 일정 주기로 isHidden이 toggle되는 것처럼 보이다가 '파밧'하고 누가봐도 비정상 동작처럼 보이는 동작이 발생하는 것을 확인하실 수 있습니다.

 

 

 

이런 것은 '왜', '무엇 때문에' 발생하는 것일까요?!🤔

 

저는 '잠시 후에 어떤 작업을 실행해'라는 것을 보통 DispatchQueue.main.asyncAfter(deadline:execute:)라는 메서드를 통해 구현을 했었는데요.

아마 앞의 조건에 대한 코드를 구현하라고 하면 저와 같은 메서드를 떠올리는 분들이 많지 않을까 싶습니다.(아닌가요..?!🙉) 

 

이 문제에 대한 원인은 GCD에서 다루는 DispatchQueue 그리고 asyncafter(deadline:execute:)메서드와 관련이 있습니다.

더보기

iOS에서 GCD는 필수라고 생각하는 부분이라 아직 GCD에 대한 부분이 이해가 부족하시다면 이번 기회에 GCD를 공부해 보시면서 '왜', '무엇때문에'를 같이 고민해보신다면 좋을 것 같습니다.  

 

이미 잘 알고 계시고 문제가 무엇 때문인지 잘 아시는 분들이라면...멋져요 따봉을 드립니다. 👍🏻ㅎㅎ

 

저는 원인을 찾았고, 결과는 다음과 같았습니다.(접은 글 참고)

더보기

 '잠시 후에 어떤 작업을 실행해'라는 일정 시간 delay 후 동작하는 code가 중복되서 실행되는 것을 방지하기 위해 '취소'가 필요했습니다. 

 

코드의 '취소'와 관련된 것들을 찾다보니 그 중 하나가 DispatchWorkItem, Timer, 그리고 OperationQueue를 이용한 방법들이 있었는데, 적용에 고민한 부분은 기존 레거시 코드에서 팀원들이 같이 알고 있을 것이라고 보여지고, 이해하기 쉬울 것이라고 보여지는 것은 어떤 것일까 였습니다.(여기에는 이유가 있기 때문에 고민하게 됐던 것 같습니다.)

 

좁혀지는 부분은 DispatchWorkItem, Timer였고, 그 중 저는 이전에 사용해 본적이 있는 DispatchWorkItem을 이용해서 필요 기능을 구현해봤습니다.

 

코드 예시는 다음과 같습니다.

var workItem: DispatchWorkItem?
	
@IBAction func togglePanelIshidden(_ sender: UITapGestureRecognizer) {
    if panelView.isHidden {
        panelView.isHidden.toggle()

        workItem?.cancel()
        workItem = DispatchWorkItem { [weak self] in
            guard let self = self else { return }

            if let isCancelled = self.workItem?.isCancelled, !isCancelled {
                self.panelView.isHidden = true
            }
        }

        if let workItem = workItem {
            DispatchQueue.main.asyncAfter(deadline: .now() + 5, execute: workItem)
        }

    } else {
        panelView.isHidden.toggle()
        workItem?.cancel()
    }
}

위와 같이 특정 뷰를 몇 초 뒤에 hidden처리 하는 코드를 DispatchWorkItem을 통해 캡슐화하고, 그 코드를 DispatchQueue.main.asyncAfter(deadline:execute:)의 execute 파라미터로 전달합니다.

 

그리고, 그 코드가 실행되기 전에 cancel()을 호출해서 아래와 같이 isCancelled의 값을 확인해서 cancelled가 된 것이 아니라면 숨김처리를 하고, cancelled가 된 것이라면 아무것도 하지 않는 방식으로 코드를 작성해서 처리를 했었습니다.

뭔가 네이밍으로만 생각해봤을 때, 위와 같이 내용을 봤을 때는 cancel()메서드가 호출되면 취소가 되고, 취소가 됐으니 isCancelled는 상태 값이 변하고, 그 값을 변하게 해놓고 workItem을 만드니 이거 안되는거 아니야? 라고 생각하실 수도 있지만 답은 cancel()메서드의 Discusstion부분을 보시면 이해에 도움이 되실 것 같습니다.

 

 

요약

  • DispatchWorkItem은 실행 블록을 취소할 수 있습니다.
  • DispatchWorkItem은 실행 블록을 하나의 타입으로 캡슐화한 것으로, DispatchQueue.main.asyncAfter(deadline:execute:)와 같은 메서드에서 DispatchWorkItem을 파라미터로 받는 메서드로 전달해서 기존의 실행 블록(클로저)로 실행되는 것과 동일한 기능을 합니다.
  • DispatchQueue.main.asyncAfter(deadline:execute:)의 코드를 사용할 때 queue에 '잠시 후에 어떤 작업을 실행해'라는 것을 넣고 반복적 사용이 필요할 때는 비정상 동작에 대한 고려가 필요합니다.
  • DispatchQueue.main.asyncAfter(deadline:execute:)의 코드의 반복에 의해 비정상 동작에 대한 해결은 cancel()로 가능하다.
  • cancel()을 하는 것은 DispatchWorkItem, Timer, OperationQueue에서도 가능합니다.

 

'IOS' 카테고리의 다른 글

Property List(.plist) 2  (0) 2023.08.10
Property List(.plist) 1  (0) 2023.08.09