← 메인으로

0818-2 스위프트 구조화된 동시성 개요

동시성은 여러 작업을 병렬로 수행하는 소프트웨어 기능으로 정의할 수 있다. 많은 앱 개발 프로젝트는 어느 시점에서 동시 처리를 사용해야 하며, 동시성은 우수한 사용자 경험을 제공하는 데 필수적이다. 예를 들어, 동시성은 앱의 사용자 인터페이스가 이미지 다운로드 또는 데이터 처리와 같은 백그라운드 작업을 수행하는 동안 앱의 응답성을 유지하도록 해준다.

스위프트 프로그래밍 언어의 구조화된 동시성 기능을 살펴보고 이러한 기능을 사용하여 앱 프로젝트에 멀티테스킹 지원을 추가하는 방법을 공부해보자.

스레드 개요

스레드(thread)는 현대 CPU의 기능이며 모든 멀티테스킹 운영체제에서 동시성의 기반을 제공한다. 현대 CPU는 많은 수의 스레드를 실행할 수 있지만, 한 번에 병렬로 실행할 수 있는 실제 스레드 수는 CPU 코어 수에 의해 제한된다.(CPU 모델에 따라 일반적으로 4~16개 코어) CPU 코어보다 많은 스레드가 필요한 경우 운영체제는 스레드 스케줄링을 수행하여 이들 스레드의 실행을 사용 가능한 코어 간에 어떻게 공유할지를 결정한다.

스레드는 메인 프로세스 내에서 실행되는 미니 프로세스로 생각할 수 있으며, 그 목적은 앱 코드 내에서 병렬 실행의 형태를 가능하게 하는 것에 있다.

다행스럽게도 구조화된 동시성이 백그라운드에서 스레드를 사용하지만 모든 복잡성을 처리하므로 우리가 직접적으로 상호작용할 필요가 없다는 것이다.

앱 메인 스레드

앱이 처음 시작될 때 런타임 시스템은 보통 앱이 기본적으로 실행되는 단일 스레드를 생성한다. 이 스레드를 일반적으로 메인 스레드라고 한다. 메인 스레드의 주요 역할은 UI 레이아웃 렌더링, 이벤트 처리 및 사용자 인터페이스에서 뷰와 사용자 상호작용 측면에서 사용자 인터페이스를 처리하는 것이다.

메인 스레드를 사용해 시간 소모적인 작업을 수행하는 앱 내의 다른 코드는 시간 소모적인 작업이 완료될 때까지 전체 앱이 멈춘 것처럼 보이게 한다. 이것은 메인 스레드가 다른 작업을 방해받지 않고 계속할 수 있도록 수행할 작업을 별도의 스레드에서 시작해 피할 수 있다.

[!note] 이해하고 넘어가자. 아이폰 앱이 실행되면 메인 스레드라는 기본 실행 줄기가 생기는데, 이 역할은 주로 UI 관련이다. 그래서 메인 스레드는 절대 오래 걸리는 작업(서버에서 데이터 받기, 큰 파일 읽기)을 하면 안된다. 왜? 그동안 화면이 멈춘 것처럼 보이기 때문이다.

완료 핸들러

오래 걸리는 작업은 메인 스레드가 아닌 다른 스레드(백그라운드)에서 실행된다. 그런데 작업이 끝난 후에는 결과를 UI에 보여줘야 한다. 이때 완료 핸들러라는 코드를 준비해둔다.

func downloadImage(url: String, completion: @escaping (UIImage?) -> Void) { DispatchQueue.global().async { // 백그라운드에서 실행 let data = try? Data(contentsOf: URL(string: url)!) let image = data.flatMap { UIImage(data: $0) } // 완료되면 completion 호출 DispatchQueue.main.async { completion(image) // 메인 스레드에서 UI 업데이트 } } } // 사용하기 downloadImage(url: "https://example.com/image.png") { image in myImageView.image = image // 완료 후 실행될 코드 } 
completion: @escaping (UIImage?) -> Void 
{ image in myImageView.image = image } 

완료 핸들러 방식의 문제점

구조화된 동시성(async / await)

구조화된 동시성은 스위프트 버전 5.5와 함께 스위프트 언어에 도입되어 앱 개발자가 동시 실행을 보다 쉽고 논리적이고 작성하고, 이해하기 쉬운 방식으로 안전하게 구현할 수 있도록 한다. 다시 말해 구조화된 동시성 코드는 논리 흐름을 이해하기 위해 완료 핸들러 코드로 다시 이동할 필요 없이 위에서 아래로 읽을 수 있다.

또한, 구조화된 동시성은 비동기 함수에서 발생하는 오류를 더 쉽게 처리할 수 있도록 한다. 쉽게 말해, async/await를 이용해 비동기 흐름을 마치 동기 코드처럼 단순하게 작성할 수 있는 방법.

import SwiftUI struct ContentView: View { var body: some View { Button(action: { doSomething() }) { Text("Do Something") } } func doSomething() { } func takesTooLong() { } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } }