https://developer.apple.com/videos/play/wwdc2021/10095/
Use async/await with URLSession - WWDC21 - Videos - Apple Developer
Discover how you can adopt Swift concurrency in URLSession using async/await and AsyncSequence, and how you can apply Swift concurrency...
developer.apple.com
async/await가 URLSession과 어떻게 작동하는지 살펴보자 (0:00)

- Swift Concurrency는 코드를 직관적이고 선형적으로 만들고, Swift의 네이티브 에러 처리와 잘어울림
- 네트워킹은 본질적으로 비동기이기 때문에 URLSession에서 async/await이 매우 적합함
- iOS 15, macOS Monterey부터 URLSession은 새로운 async API를 제공함
반려견을 좋아하는 사람들을 위한 사진 공유 앱을 보자
- 즐겨찾기 기능
- 사진을 가져오는 기능

Completion Handler를 사용한 코드

- URLSession을 사용하는 completionHandler 기반의 메서드
- 해당 코드는 세 가지 실수가 있음
이 코드의 제어 흐름(Control Flow)을 보자

- URLSession.shared.dataTask를 통해 작업을 만듦
- 해당 작업을 재개함
- 작업이 완료되면 completion Handler로 이미지 생성 후 흐름을 끝냄
→ 앞뒤로 점프하며 제어 흐름이 왔다갔다 함
이 코드의 스레딩을 살펴보자
이 짧은 코드에도 놀라울 정도로 복잡하다
메서드에는 총 세 가지의 실행 컨텍스트가 있음

- 가장 바깥 쪽 계층: fetchPhoto(url: completionHandler:)
- 해당 메서드를 호출한 호출자의 스레드 또는 큐에서 실행됨
- URLSessionTask completionHandler
- 세션의 대리자 큐에서 실행 (session’s delegate queue)
- 최종 completion Handler
- 메인 스레드에서 실행 (DispatchQueue.main.async)
→ 컴파일러는 에러를 잡아주지 않기 때문에 Data Race 같은 스레딩 문제를 피하기 위해선 개발자가 주의해야 함
💡 Session’s Delegate Queue란?
1. URLSession의 실행 구조
- 요청/응답, 인증 처리, 리다이렉트, 데이터 수신 이벤트 등이 발생하면 → delegate에게 알림을 보냄
- 이때 delegate 메서드가 실-행되는 큐(DispatchQueue/OperationQueue)를 delegateQueue라고 부름
2. URLSession은 내부적으로 delegate 패턴을 기반으로 작동함
3. 세션 delegate 큐의 기본값
- URLSession(configuration:delegate:delegateQueue) 초기화 시 지정하지 않으면 직렬 OperationQueue를 사용함
- URLSessionTask 콜백은 main queue가 아닌, delegateQueue에서 실행됨
4. completionHandler와 delegate 큐의 관계
- 네트워크 이벤트는 내부적으로 먼저 delegateQueue에서 처리됨
- 이후 우리가 등록한 completionHandler가 그 delegateQueue에서 실행됨
- dataTask(with:completionHandler:) 같은 메서드 사용 시
→ 따라서, completionHandler는 기본적으로 메인 스레드를 보장하지 않음
이제 해당 코드에 있는 세 가지 문제를 알아보자
1. completion handler의 호출이 메인 스레드에 일관되지 않음

→ 올바른 데이터일 때만, 메인 스레드에서 실행을 보장함
2. Missing early return (return의 부재)

→ completion handler 호출 후 return 하지 않아 이후의 코드가 그대로 실행되어 completion handler를 두 번 호출할 가능성이 존재함
3. UIImage 생성 실패 가능성

→ data로 UIImage 생성에 실패했을 경우, nil을 리턴하기 때문에 completion handler에 (nil, nil)을 리턴할 가능성 있음
async/await 사용 버전

- 제어 흐름(Control Flow)이 위에서 아래로 선형적
- 이 메서드는 모두 동일한 Concurrency Context에서 실행되므로 스레딩 문제에 걱정할 필요가 없음
- URLSession에서 새로운 비동기 메서드를 사용하여 차단 없이 네트워킹 가능
- throw 키워드를 사용하여 오류 처리 가능하며, UIImage를 nil로 반환할 수 없어 컴파일러가 알려줄 것임
새로운 URLSession 메서드를 살펴보자

- URLSession.data 메서드는 기존과 마찬가지로 URL, URLRequest를 모두 허용함
데이터를 업로드하거나 파일을 업로드하는 메서드도 제공

→ 기존과 거의 동일하지만, 기본 HTTP 메서드를 제공하지 않으므로 요청 전 설정 필요함
다운로드 메서드

- 다운로드 메서드는 응답 본문을 메모리(Data)가 아닌 파일로 저장함
- 이 메서드는 파일을 자동으로 삭제하지 않으므로 직접 삭제해야 함!!
- 해당 예제에는 추가 처리를 위해 파일 위치를 이동하는 예제
💡 URLSession.download - 개념 잡기
- 네트워크 응답 본문을 메모리(Data)가 아닌 파일로 저장해서 반환함
- 큰 파일(영상, 압축, 큰 이미지 등)에 유리
- 반환 값
let (fileURL, response) = try await URLSession.shared.download(from: someURL)
// fileURL: 임시(temporary) 위치의 로컬 파일 URL
// response: URLResponse (대부분 HTTPURLResponse)
- fileURL은 임시 디렉터리에 놓은 실제 파일로, 필요에 따라 원하는 경로로 직접 이동(move)하거나 복사(copy)해야 함
- 파일을 자동 삭제하지 않으므로 직접 삭제해야 함 </aside>
작업 취소

- URLSession의 async 메서드를 취소하는 방법은 Task.Handle을 사용하는 것
- 위 예제에는 두 개의 리소스를 하나씩 로드하는 동시성 작업을 만듦
- Task.Handle을 이용하여 cancel() 메서드 호출하여 작업 취소 가능
→ task.cancel()을 호출하면 Swift Concurrency 취소가 전파되어 내부의 네트워크 요청도 취소됨
💡 Task.Handle
- Swift Concurrency에서 Task { … }를 만들면, 이 작업을 제어할 수 있는 객체가 필요함 → 그 역할을 하는 게 Task.Handle
- Task: 실제 실행하는 비동기 작업
- Task.Handle: 그 작업을 외부에서 취소(cancel)하거나, 결과(await)하기 위한 핸들
- Task.Handle의 타입은 제네릭 타입
Task<Success, Failure: Error>
// Success: 작업이 반환하는 값의 타입
// Failure: 작업이 던질 수 있는 에러 타입
“Concurrency Task”와 “URLSessionTask”는 다른 개념!!
Concurrency Task
- Swift 런타임이 관리하는 동시성 단위
- Task.Handle을 통해 취소, 결과 대기 가능
URLSessionTask
- URLSession이 관리하는 네트워크 작업 단위(dataTask, uploadTask, downloadTask 등)
Response Body 점전적으로 받기
- 보통 URLSession.data는 전체 응답을 다 받은 뒤에 결과를 리턴함
- 실시간 API는 끝나지 않고 이어짐 → 전체를 가디리면 앱이 멈춤
→ 이때, URLSession.bytes 메서드 사용
- URLSession.AsyncBytes 타입을 반환
- response body를 Byte 단위로 AsyncSequence 처럼 사용

- Response Header가 수신되면 return하고 Response body를 Byte의 AsyncSequence로 전달
예제를 보자
- 즐겨찾기 개수를 실시간으로 업데이트하는 API
- EndPoint 확인

AsyncSequence API를 사용하여 실시간으로 즐겨찾기 개수 업데이트


- 반환된 바이트 타입은 URLSession.AsyncBytes
JSON 데이터 읽기

- bytes.lines: 응답 스트림을 줄 단위 비동시 시퀀스로 변환.
- for try await: 데이터가 오면 한 줄씩 사용
- JSON 디코딩 후 UI 반영
- UI 업데이트 메인 액터에서 이루어져야 하므로 await 구문을 사영하여 비동기 함수인 updateFavoriteCount 호출
💡 점진적 업데이트 (Real Time 방식)
- 보통의 요청 - 응답 방식
- 서버가 전체 응답을 다 보낼 때까지 기다렸다가 한 번에 결과를 줌
- 서버가 “전송 끝”이라는 신호를 보내기 전까진 클라이언트는 데이터를 쓸 수 없음
- 따라서, 실시간 반영이 불가능하고, 다시 요청해야 최신 데이터를 가져올 수 있음
- 스트리밍 방식 (실시간 업데이트)
- 실시간을 구현하려면 서버가 응답을 끝내지 않고 연결을 열어둔 채,
- 값이 변할 때마다 새로운 JSON 라인을 계속 밀어주는 구조로 사용
{"photoId": "123", "favorites": 42}\\n ← 처음 상태 {"photoId": "124", "favorites": 17}\\n ← 다른 사진 업데이트 {"photoId": "123", "favorites": 43}\\n ← 좋아요 수 증가!- 서버 입장에서는 “HTTP 연결 하나 열어둔 채” 실시간 이벤트 푸시
- 클라이언트는 bytes.lines 줄 단위 데이터를 받자마자 처리 가능
AsyncSequence
- AsyncSequence는 많은 변환을 지원하며,
- FileHandle과 같은 다른 시스템 프레임워크 API와 사용 가능함
https://developer.apple.com/videos/play/wwdc2021/10058/
Meet AsyncSequence - WWDC21 - Videos - Apple Developer
Iterating over a sequence of values over time is now as easy as writing a “for” loop. Find out how the new AsyncSequence protocol enables...
developer.apple.com

Task-Specific Delegate

- URLSession은 인증, 네트워크 메트릭 등과 같은 이벤트에 대한 콜백을 제공하는 Delegate 중심으로 설계됨
- 즉, 기존에는 URLSessionDelegate 등을 구현해서 콜백 메서드로 이벤트를 받는 방식이었음
async/await API에는 task가 드러나지 않음
→ async 메서드는 URLSessionTask 객체를 직접 반환하지 않음
즉, 예전처럼 task.delegate를 붙여서 이벤트 처리하기가 불가능해짐
그렇다면 특정 task에만 적용되는 인증 처리 같은 건 어떻게 할까?
Task-specific delegate
- 이제 각 async 메서드 호출 시 delegate 파라미터를 넘길 수 있음
- 특정 작업에만 delegate 지정 가능
- 이 delegate 객체는 그 task가 완료되거나 실패할 때까지 강하게 참조되어 유지됨
- 세션 자체가 아닌 개별 task 이벤트 처리에 적합
- 단, background URLSession은 지원하지 않음
- 예시) 인증 필요 시 로그인 창 호출

동일한 기능을 활용할 수 있또록 Objective-C의 NSURLSessionTask의 Delegate 속성을 도입함

💡 어떤 상황에서 유용한가?
예시: Dogs 앱
- 사진을 가져오는 건 인증 필요 없음 → 일반 async 호출
- “사진 즐겨찾기” 인증 필요 → Unauthorized 발생 가능
- 이때, 인증이 필요한 task에만 AuthenticationDelegate를 넣어주면 됨
즉, 모든 task에 공통으로 적용할 필요 없이, 특정 task에만 딜리게이트 로직 적용 가능

- AuthenticationDelegate는 URLSessionTaskDelegate 프로토콜을 준수하며,
- 이니셜라이저에 signInController 인스턴스를 허용함
- signInController 클래스에는 사용자에게 자격증명을 묻는 메시지를 표시하는 몇 가지 함수가 이미 포함됨
URLSession didReceive 메서드 구현
class AuthenticationDelegate: NSObject, URLSessionTaskDelegate {
private let signInController: SignInController
init(signInController: SignInController) {
self.signInController = signInController
}
func urlSession(_ session: URLSession,
task: URLSessionTask,
didReceive challenge: URLAuthenticationChallenge) async
-> (URLSession.AuthChallengeDisposition, URLCredential?) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic {
do {
let (username, password) = try await signInController.promptForCredential()
return (.useCredential,
URLCredential(user: username, password: password, persistence: .forSession))
} catch {
return (.cancelAuthenticationChallenge, nil)
}
} else {
return (.performDefaultHandling, nil)
}
}
}
- 사용자에게 자격 증명을 묻는 메시지를 표시하여 HTTP 인증에 응답하도록 선택할 수 있음
- 에러 처리 됨
💡 task-specific delegate 정리
- 기존 세션 델리게이트: 세션 전체에 공통 적용 (모든 task 이벤트 처리)
- task-specific delegate: 개별 task에만 적용, async/await 방식에도 사용 가능
- 제한: background 세션 </aside>
결론

- async/await과 URLSession을 함께 쓰면
- 가독성, 안정성, 에러 처리 모두 개선
- 실시간 이벤트는 AsyncSequence로 대체 가능
- 추가로 확인할 주제
'WWDC' 카테고리의 다른 글
| WWDC 2021 - Protect mutable state with Swift actors 정리 (0) | 2025.09.09 |
|---|---|
| WWDC 2021 - Meet AsyncSequence 정리 (0) | 2025.09.08 |
| WWDC 2021 - Discover concurrency in SwiftUI 정리 (0) | 2025.09.04 |
| WWDC 2021 - Explore structured concurrency in Swift (1) | 2025.09.03 |
| WWDC 2021 - Meet async/await in Swift 정리 (2) | 2025.09.01 |