iOS

Swift의 비동기 프로그래밍 (GCD)

미니미니니

비동기 프로그래밍이란 여러가지 작업을 여러 스레드에서 동시에 처리하여 성능을 향상시켜주는 기술입니다.

앱에서 예를 들어서 용량이 큰 이미지나 파일을 다운받는동안 아무작업도 못하고 가만히 기다려야한다면 사용자들은 답답함을 느낄 것입니다. 이때 여러 스레드를 사용하여 비동기 프로그래밍을하면 파일을 받는동안 앱에서 다른 작업(ex. 글 쓰기, 게시물 보기 등)을 할 수 있게 됩니다. 

swift에서 제공하는 여러가지 비동기 프로그래밍 방식이 있지만 첫번째로 GCD에 대해서 알아보도록 하겠습니다.

Grand Central Dispatch (GCD)

GCD는 Apple에서 제공하는 비동기 프로그래밍 기술로 개발자가 직접 스레드를 관리하지 않고 원하는 작업을 큐(Queue)에 보내기만 하면 시스템이 여러 스레드에 작업을 분배하고 실행시켜줍니다. 

원하는 작업을 큐로 보내는 방법은 아래와 같습니다.

DispatchQueue.global().async {
    sleep(3) // 3초 걸리는 스테이크
    print("🥩 스테이크 완성!")
}
DispatchQueue.global().async {
    sleep(1) // 1초 걸리는 샐러드
    print("🥗 샐러드 완성!")
}

// 결과
// 🥗 샐러드 완성!
// 🥩 스테이크 완성!

 

여기서 DispatchQueue가 바로 위에서 GCD에서 큐에 작업을 보내면 된다고하는 "큐"입니다.

뒤에 나오는 .global은 기본적으로 제공되는 큐의 종류중 하나인데, 제공되는 큐의 종류에는 global과 main이 있습니다.

  • Main Queue: UI 업데이트를 전담하는 직렬 큐입니다. 화면에 보이는 모든 것(버튼, 텍스트, 애니메이션 등)은 반드시 메인 큐에서 처리해야 합니다.
  • Global Queue: 시간이 걸리는 일반적인 백그라운드 작업을 처리할 때 사용합니다. 지정하는 작업의 중요도(QoS)에 따라 시스템이 스레드를 효율적으로 분배합니다.

QoS (Quality of Service) 종류

  • userInteractive: 사용자의 터치에 즉각 반응해야 하는 작업. (UI 렌더링이 아님)
  • userInitiated: 사용자가 결과를 즉시 기다리는 작업 (문서 열기, 버튼 클릭 후 계산)
  • default: 기본. userInitiated와 utility의 중간정도의 우선순위
  • utility: 시간이 좀 걸리지만 진행 상태를 알려주는 작업 (네트워크 통신, 데이터 다운로드)
  • background: 사용자가 인지하지 못하는 백그라운드 작업 (백업, 데이터베이스 정리)

보통 비동기 작업은 Global Queue에 할당하지만 화면을 그리는 것과 같은 UI 작업은 꼭!! Main Queue에서 작업해야합니다.

// Global Queue에서 작업 시작
DispatchQueue.global(qos: .userInitiated).async {
    // 백그라운드에서 데이터 로딩 (시간이 걸리는 작업)
    let data = loadDataFromNetwork()

    // 메인 큐에서 UI 업데이트
    DispatchQueue.main.async {
        updateUI(with: data)
    }
}

 

DispatchGroup

여러개의 비동기 작업을 같이 실행하고 모든 작업이 끝난 후 특정 코드를 작성하고 싶을 때 사용할 수 있습니다.

 

  • enter(): 그룹에 작업 하나가 추가되었음을 알립니다.
  • leave(): 그룹의 작업 하나가 끝났음을 알립니다.
  • notify(): 그룹에 있던 모든 작업이 끝났을 때 실행될 코드를 등록합니다. (비동기 방식)
  • wait(): 모든 작업이 끝날 때까지 현재 스레드를 멈추고 기다립니다. (동기 방식)
let group = DispatchGroup()

print("API 동시 요청 시작")

group.enter() // 작업 1 추가
fetchImageFromServer(url: url1) { image in
    print("이미지 1 다운로드 완료")
    group.leave() // 작업 1 완료
}

group.enter() // 작업 2 추가
fetchUserDataFromServer(id: "user123") { data in
    print("유저 정보 다운로드 완료")
    group.leave() // 작업 2 완료
}

group.notify(queue: .main) {
    print("✅ 모든 다운로드가 완료되었습니다!")
    // UI 업데이트 등 최종 작업 수행
}