본문 바로가기
iOS/iOS

동시성 프로그래밍 (5) - GCD 사용 시 주의해야할 사항

by 패쓰킴 2024. 8. 21.
728x90

GCD를 사용할 때에 주의 할 부분과 응용방법!

 

1.  반드시 메인큐에서 처리해야하는 작업

우리는 화면을 그리고 그 화면에 필요한 데이터를 가져오고 가공하는 여러 작업들을 하게 된다.

그리고 별도로 스레드로 작업을 분산하지 않는다면 보통은 대부분의 작업이 메인스레드에서 처리가 되는데 메인 스레드는 앱의 UI를 관리하고 사용자와의 상호 작용을 처리하는 매우 중요한 스레드이다. 따라서 UI관련 작업이나 애니메이션 그리고 사용자가 텍스트를 입력할 때 처리해야하는 작업 등은 반드시 메인스레드에서 처리되도록 해야한다.

왜 메인 스레드에서 UI 작업을 해야 할까?

1) UI 일관성 유지: 여러 스레드에서 동시에 UI를 변경하면 예기치 못한 결과가 발생할 수 있다. 메인 스레드에서만 UI를 변경함으로써 UI 상태를 일관되게 유지한다.
2) 데드락 방지: 다른 스레드에서 UI를 변경하려고 할 때, 메인 스레드가 다른 작업을 처리 중이라면 데드락이 발생할 수 있다. 메인 스레드에서 UI 작업을 집중시킴으로써 이러한 문제를 방지할 수 있다.
3) 간단한 코드 관리: 모든 UI 관련 작업을 메인 스레드에서 처리하면 코드 관리가 훨씬 간단해진다.

 

2. sync를 사용할 땐 주의하자

1) 메인큐에서는 다른큐로 보낼 때 sync 메서드를 부르면 절대 안된다.

sync로 작업을 보내게 되면 동기적으로 처리가 되므로 화면이 버벅이는 현상이 발생한다. 그러므로 메인큐에서는 항상 비동기적으로 보낼 것!

 

2) 현재의 큐에서 현재의 큐로 동기적으로 보내서는 안된다.

이 말은 현재의 큐를 블락하는 동시에(=sync하는 동시에), 다시 현재의 큐에 접근하는 상태를 말하는데 이러한 상황은 데드락을 발생시킨다.

예를 들어,

DispatchQueue.global().async {
  DispathQueue.global().sync {
    // code
  }
}

 

thread2에서 async로 작업하는 것을 sync로 다시 대기열에 올렸을 때 문제가 발생한다. 이 작업이 현재 작업중이던 thread2에 배치되면 교착상태가 발생할 가능성이 있으므로 주의해야한다.

 

3. weak, strong 캡처 시 주의하자

대기열에 작업을 보내는 DispatchQueue, OperationQueue는 클로저를 보내는 일이기 때문에 객체에 대한 캡처 현상이 발생한다.

DispatchQueue.global().async { 
  // code
}

이렇게 아무 처리도 해주지 않으면 strong 캡처가 발생하여 reference count가 증가하는데

DispatchQueue.global().async { [weak self] in
  // code
}

weak 캡처로 옵셔널 타입으로 진행하게 되면 약한 참조로 인해 reference count가 올라가지 않는다. 

 

4. (비동기 작업에서) 작업이 끝나는 시점을 알아야 할 때

비동기로 작업을 처리 할 경우 작업이 끝나는 시점을 알기가 어렵다. 이 끝나는 시점을 명확히 알아야 할 때가 있는데 이때 사용하는 것이 completionHandler이다.

completionHandler는 어떤 작업이 끝났음을 알리는 클로저로 작업의 순서를 만들 때 편리하다.

func task1(completionHandler: @escaping ()->()) {
  completionHandler()
}

func task2() {
  task1() {
    print("작업1 완료")
    print("작업2 시작가능")
  }
}

 

5. 동기적 함수를 비동기 함수 처럼 만드는 법

여러 번 재활용 하기 위해 동기함수를 비동기함수처럼 만들어야 할 때가 있다.

만약 아래와 같이 이미지의 그래픽을 처리하기 위한 동기적(오래걸리는) 함수가 있다면

func tiltShift(image: UIImage) {

}
func asyncTiltShift(_ inputImage: UIImage?, runQueue: DispatchQueue, completionQueue: DispatchQueue, completion: @escaping (UIImage?, Error?) -> ()) {

  // 1) 동기 함수를 직접적으로 작업할 큐
  runQueue.async {
    var error: Error?
    error = .none

    let outputImage = tiltShift(image: inputImage)

    // 2) 작업을 마치고나서의 큐
    completionQueue.async {

      // 3) 컴플리션 핸들러 (작업이 끝난 시점을 알리기 위한)
      completion(outputImage, error)
    }
  }
}
728x90

댓글