스파르타코딩 클럽/본 캠프

46. 스파르타 코딩클럽 [본캠프 - 온보딩 46일차]

UDDT 2025. 5. 8. 09:00

46일차 - 강의 주차

앱 개발 심화 주차 - 동기, 비동기

 

    동기와 비동기를 이해하기 전, 프로세스와 스레드를 먼저 보자

   프로세스와 스레드 모두 컴퓨터에서 작업을 수행하는 흐름(단위)임

   실행 중인 iOS 앱 : 프로세스. 그리고 프로세스 내부에는 여러 개의 스레드가 존재함

1.  프로세스와 스레드 

     - 프로세스(Process) : 실행 중인 프로그램(앱을 실행하기 위해 메모리 위에 올라온 프로그램)

        iOS로 치면, '앱' 또한 iOS 운영체제에 속한 프로그램임

        iOS앱을 실행한 것 또한 프로세스라 부를 수 있음

        ex) 유튜브 실행 전 : 프로그램

              실행 후 : 프로세스

      

     - 스레드(Thread) : 프로세스 내에서 작업을 수행하는 단위(한 개의 프로세스 안에 여러개의 스레드가 동시 작업 가능 - 멀티 스레딩)

        실행 중인 iOS앱이 프로세스라면, 실행 중일 때 여러가지 스레드를 가지고 있다는 말

        크게는 메인 스레드와 백그라운드 스레드로 구분 

    

    - 식당 비유로 프로세스와 스레드 쉽게 이해하기

     프로세스 : 피자 가게

     스레드 : 요리사

     

     피자 레스토랑은 "고객에게 피자를 준다"는 작업을 수행함

    피자를 제공하기 위해 여러 명의 요리사가 동시에 다른 작업을 수행할 수 있음

    A 요리사: 도우 준비  / B 요리사: 소스 만들기 / C 요리사: 토핑 올리기, 굽기

   

2. 동기 / 비동기

 

    1. 동기(synchronous, sync)

        어떤 스레드에서 작업 A를 처리하다가 새로운 작업 B가 들어오면,

      그 작업 B를 수행하고 완료될 때까지 기다렸다가 다시 작업 A를 수행하는 작업처리 방식

      > 직렬적인 작업 수행

 

    2. 비동기(asynchronous, async)

        어떤 스레드에서 작업 A를 처리하다가 새로운 작업 B가 들어오면,

      다른 스레드에 작업 B를 넘기고, 수행하고 있던 작업 A는 멈추지 않고 병렬적으로 동시에 수행하는 작업처리 방식.

      작업 B가 완료되면, 완료되었다는 결과와 소식을 전해받음

      > 병렬적인 작업 수행

 

     - 예시)

        A 스레드에서 UI를 그리는 작업을 하고 있는데,

        네트워크 통신을 해서 이미지 데이터를 받는 작업이 들어온 상황

 

        * 동기일 때,

           UI를 그리다가 멈추고, 네트워크 통신 후 이미지 데이터를 받은 뒤 결과가 도착하면 다시 UI를 그리는 작업 시작

        * 비동기 일때,

           UI를 그리다가, 네트워크 통신 작업이 들어오면 B 스레드에 작업을 넘기고 UI 작업은 이어서 계속 수행.

           서버 결과가 돌아왔을 때 응답을 돌려받음.

 

        앱에서 처리해야하는 작업을 하나의 스레드에서만 작업하게 되면 앱의 성능이 매우 떨어지게 됨.

      따라서 개발자는 동기 / 비동기와 스레드를 통해 효율적으로 작업을 처리해야 함

 

3. DispatchQueue, GCD

     - GCD(Grand Central Dispatch)

    https://developer.apple.com/documentation/DISPATCH

 

Dispatch | Apple Developer Documentation

Execute code concurrently on multicore hardware by submitting work to dispatch queues managed by the system.

developer.apple.com

 

    GCD는 동시성 프로그래밍을 돕는 프레임워크.(iOS, MacOS, WatchOS 등에 사용할 수 있는 동시성 프레임워크)

   동시성 프로그래밍이란 여러가지 일을 동시에 작업하도록 하는 프로그래밍을 의미하며,

   Swift에서 사용하는 GCD 기술이 바로 DispatchQueue이고, GCD 안에 이 DispatchQueue가 포함되는 것

 

   멀티 스레딩 작업. 즉, 비동기적으로 여러가지 작업을 수행하기 위해서는 DispatchQueue를 사용하면 됨

 

  * DispatchQueue란?

    Dispatch(보내다, 발송하다) | Queue(자료 구조)

 

  * Queue란?

 

Queue

 

        먼저 들어온 자료부터 먼저 사용하는 것(선입선출).

       우선순위 큐(Priority Queue)는 데이터가 나가는 우선순위 규칙을 부여하고, 우선순위에 따라 데이터를 내보냄

      

    - 과거 프로그래밍 방식

       멀티 스레딩을 하기 위해서 개발자가 직접 스레드를 생성하고, 명시하고, 어떤 스레드에 작업을 할당해줄지 직접 골라야 했음(피곤)

 

    - 현재 프로그래밍 방식

       위의 어려움을 해결하는 시스템이 바로 DispatchQueue!

       작업이 생겼을 때 DispatchQueue한테 작업을 넘겨주면, 운영체제가 알아서 적절한 스레드에 할당해줌(편안)

 

4. DispatchQueue 기본

     - DispatchQueue 기본 형태          * Qos는 별도의 챕터로 이해

DispatchQueue.{큐종류}.{qos옵션}.{sync/async} {
    // 수행할 코드
}

/*
 큐 종류 : main, global, custom
 qos : quality of service
 sync : 동기적으로 작업 수행
 async : 비동기적으로 작업 수행
*/

 

     - DispatchQueue 종류

        1. 메인 큐(maint Queue) :

            UI 업데이트와 관련된 작업을 처리

            앱의 메인 스레드에서 실행

            Serial Queue(직렬 큐)

       

        2.  글로벌 큐(global Queue):

            백그라운드 작업을 처리

            Concurrent Queue(동시성 큐)

 

        3. 커스텀 큐(custom Queue):

            개발자가 직접 생성하고 관리하는 큐

            직렬(Serial) 또는 동시(Concurrent)큐로 설정 가능

 

5. Serial vs Concurrent

    - Serial Queue(직렬 큐) :

       들어온 작업들을 한가지 스레드에 모두 보내는 큐

       큐에 추가된 작업을 한 번에 하나씩 순서대로 실행

       스레드 하나에 모든 작업을 할당하기 때문에 작업 완료 순서가 보장됨

 

    - Concurrent Queue(동시성 큐):

       큐에 추가된 여러 작업을 동시에 실행할 수 있는 큐

       여러 스레드에서 작업을 동시에 병렬적으로 실행 

       작업의 완료 순서는 보장되지 않음

 

6. DispatchQueue 사용법

      - mainQueue 사용법    *main에서 사용할 때는 반드시 async를 사용함! (dead Lock 현상 방지)

DispatchQueue.main.async {
   self.label.text = "작업 완료!"
}

 

      - globalQueue 사용법

DispatchQueue.global().async {
   // 네트워크 통신 또는 계산이 무거운 작업(대용량 이미지)을 수행할 때는 global Queue에서 작업
   let result = self.someHeavyComputation()
   
   DispatchQueue.main.async {
       // UI에 적용할 때는 main으로
       self.updateUI(with: result)
   }
}

 

      - customQueue 사용법

/*
label에는 Queue의 고유한 이름 설정
attributes에는 serial / concurrent 설정
설정하지 않았을 때의 default는 serial
*/
let customQueue = DispatchQueue {
    label: "com.myapp.customqueue",
    attributes: .concurrent
}

customQueue.async {
    //커스텀 큐에서 실행할 작업
}

 

7. Qos(Quality Of Service)

   - Qos : 작업의 중요도를 시스템에 알려주는 방법

             이를 통해 시스템은 리소스(CPU, 시간, 에너지 등)을 효율적으로 분배가 가능함

             작업의 중요도를 알려 작업을 할당하는데 도움을 주는 것으로 이해

 

  1. User Interactive(최고 우선순위):

      - 지금 당장 해야하는 작업

      - 특징: 매우 빠르게 처리되어야하는 작업

      - 예시: UI 업데이트, 애니메이션 등

 

  2. User Initiated(높은 우선순위):

     - 사용자가 기다리고 있는 작업

     - 특징: 몇 초 안에 완료되어야 하는 작업

     - 예시: 버튼 클릭 후 데이터 로딩

 

  3. Default(기본 우선순위):

     - 평범한 작업

     - 특징: 특별한 지정이 없을 때 사용

     - 예시: 일반적인 백그라운드 작업

 

  4. Utility(낮은 우선순위):

     - 천천히 해도 되는 작업

     - 특징: 시간이 걸리지만 즉시 필요한 것은 아닌 작업

     - 예시: 데이터 다운로드, 백업

 

  5. Background(최저 우선순위):

     - 언제 끝나도 상관없는 작업

     - 특징: 사용자가 직접적으로 인지하지 못하는 작업

     - 예시: 대용량 데이터 정리, 동기화 등

 

  6. Unspecified(시스템 결정):

     - 어떤 우선순위로 처리해도 상관없는 작업

     - 특징: 시스템이 알아서 우선순위 결정

 

8. SerialQueue + sync + async 예제

let serialQueue = DispatchQueue(label: "com.myapp.myqueue")

serialQueue.sync {
    print("Task 1이 시작되었습니다")

    Thread.sleep(forTimeInterval: 2)

    print("Task 1이 종료되었습니다")
}

print("Hello 1")

serialQueue.async {
    print("Task 2가 시작되었습니다")

    Thread.sleep(forTimeInterval: 2)

    print("Task 2가 끝났습니다")
}

print("Hello 2")

serialQueue.async {
    print("Task 3이 시작되었습니다")

    Thread.sleep(forTimeInterval: 2)

    print("Task 3이 끝났습니다")
}

  

       빌드를 계속 하다보면, 어떤 때는 "Hello2"가 "Task 2가 시작되었습니다"보다 먼저 나오고

      어떤 때는 "Task 2가 시작되었습니다"가 "Hello2"보다 먼저 나온다

serialQueue.async {        // async로 작업을 등록하고
    print("Task 2가 시작되었습니다")          // 실행

    Thread.sleep(forTimeInterval: 2)

    print("Task 2가 끝났습니다")
}

print("Hello 2")     // 바로 실행

 

     그 이유는 주석에 작성되어 있는 차이 때문.

   "Task 2가 시작되었습니다"는 큐에 등록된 이후에 실행되고, "Hello 2"는 main 스레드에서 바로 실행되기 때문이다

 

9. ConcurrentQueue + sync + async 예제

let concurrentQueue = DispatchQueue(label: "com.myapp.myqueue", attributes: .concurrent)
concurrentQueue.sync {
    print("Task 1이 시작되었습니다")

    Thread.sleep(forTimeInterval: 4)

    print("Task 1이 끝났습니다")
}

concurrentQueue.async {
    print("Task 2가 시작되었습니다")

    Thread.sleep(forTimeInterval: 3)

    print("Task 2가 끝났습니다")
}

concurrentQueue.async {
    print("Task 3이 시작되었습니다")

    Thread.sleep(forTimeInterval: 3)

    print("Task 3이 끝났습니다")
}

 

10. ConcurrentQueue + sync + async + qos 예제

     * qos 우선순위 : UserInteractive > User Initiated > Default > Utility > background > Unspecified

let concurrentQueue = DispatchQueue(label: "com.myapp.myqueue", attributes: .concurrent)

concurrentQueue.sync {
    print("Task 1이 시작되었습니다")
    Thread.sleep(forTimeInterval: 2)
    print("Task 1이 끝났습니다")
}

concurrentQueue.async(qos: .background) {
    print("Background Task가 시작되었습니다")
    Thread.sleep(forTimeInterval: 2)
    print("Background Task가 끝났습니다")
}

concurrentQueue.async(qos: .userInteractive) {
    print("UserInteractive Task가 시작되었습니다")
    Thread.sleep(forTimeInterval: 2)
    print("UserInteractive Task가 끝났습니다")
}

concurrentQueue.async(qos: .utility) {
    print("Utility Task가 시작되었습니다")
    Thread.sleep(forTimeInterval: 2)
    print("Utility Task가 끝났습니다")
}