WWDC

WWDC 2021 - Discover concurrency in SwiftUI 정리

Goniii 2025. 9. 4. 17:43
https://developer.apple.com/videos/play/wwdc2021/10019/?time=144
 

Discover concurrency in SwiftUI - WWDC21 - Videos - Apple Developer

Discover how you can use Swift's concurrency features to build even better SwiftUI apps. We'll show you how concurrent workflows interact...

developer.apple.com

 

Swift 5.5 동시성 기능이 SwiftUI와 어떻게 연결되는지

 

목차

  1. Concurrent data models (새로운 도구가 데이터 모델을 더욱 개선하는 데 어떻게 도움이 되는지)
  2. SwiftUI and the main actor (SwiftUI가 새로운 메인 액터와 어떻게 작동하는지 )
  3. New concurrency tools (동시성을 지원하는 데이터 모델을 SwiftUI 뷰에 연결하는 방법, 새 동시성 도구들)

선행 학습


우주 관련 사진을 내려받는 앱 예제 (1:31)

샘플 앱: 우주 사진 뷰어

  • 랜덤 우주 사진 목록 표시
  • 마음에 드는 사진 저장 기능
  • REST API로 데이터 가져옴 → 동시성 기능 사용하기 적합


Data Model (1:55)

Space Photo - Struct

  • 한 장의 이미지를 저장하기 위한 구조체
  • title, description, date, url에 대한 정보를 가짐
  • Codable: 네트워크 / 디스크 변환 용이
  • Identifable: SwiftUI 리스트 활용 가능

 

Photos - Class

  • 여러 SpacePhoto를 관리할 클래스
  • Photos 클래스를 ObservableObject로 만들어 데이터가 업데이트 될 때 자동으로 업데이트
  • Published 프로퍼티를 사용하여 SpacePhoto 배열을 저장
  • updateItem() 메서드: 네트워크 통신으로 받아온 데이터로 아이템 업데이트
💡ObservableObject
- SwiftUI에서 데이터 변화를 감지할 수 있는 객체
- 해당 타입으로 선언하면 SwiftUI는 이 객체가 “상태 변화를 발송(broadcast)한다고 이해
- 속성 앞에 @Published를 붙이면 그 속성이 바뀔 때마다 자동으로 알림을 보냄
- SwiftUI는 이 알림을 받아 해당 데이터를 참조하는 뷰를 자동으로 다시 그림

→ items가 바뀌면 CatalogView의 List가 자동으로 업데이트

 

 

PhotoView - View (3:03)

  • 탭 뷰 중 하나의 뷰
  • 하나의 SpacePhoto를 받아 제목을 표시
  • 리스트의 하나의 셀과 같음

 

 

CatalogView - View

  • 사진의 목록을 보여주는 뷰

  • @StateObject: Photos는 ObservableObject 타입
  • UI
    • View의 본문에 NavigationView 및 Title 추가
    • NavigationView 안에 List를 추가하고, photos를 순회하여 PhotoView 추가
    • 스타일 설정
💡StateObject
- SwiftUI 뷰 안에서 ObservableObject의 소유자가 되고 싶을 때 사용
- CatalogView가 photos라는 객체를 생성하고, 뷰의 생명주기 동안 보관
- 뷰가 렌더링 되어도 photos는 다시 초기화되지 않고 유지됨 

 

StateObject vs ObservedObject

속성 래퍼 언제 사용? 특징
@StateObject 뷰에서 객체를 직접 생성할 때 뷰의 생명주기 동안 객체를 유지
@ObservedObject 이미 다른 곳에서 만든 객체를 주입받을 때 뷰가 새로 그려질 때마다 다시 연결됨 (객체 소유 X)

 


 

SwiftU에서 ObservableObject의 상호작용과 동시성 기능이 이 상호작용을 어떻게 더 쉽게 만들어지는지 얘기해보자 (5:01)

WWDC 2020 Data Essentials in SwiftUI 에서 SwiftUI 업데이트 사이클에 대해 이야기 했다

 

이 라이프 사이클을 구동하는 코드를 “run loop”라고 부름

Swift 5.5에서는 이 런 루프가 메인 액터에서 실행됨

Protect mutable state with Swift actors 참고


 

SwiftUI run loop (6:01)

  • SwiftUI 런 루프는 사용자로부터 이벤트를 받아 모델을 업데이트 하도록 허용한 뒤, 다음 SwiftUI 뷰를 화면에 렌더링 함
  • → 이러한 업데이트를 런루프의 틱(ticks)이라 함
  • 이 루프를 풀어 여러 틱을 연속으로 살펴보자

  • updateItems 메서드는 메인 액터에서 실행됨
  • 파란 사각형은 updateItems가 실행되는 시간을 의미함
  • 중요한 부분은 가져온 사진(fetched)을 items 프로퍼티에 할당하는 부분

 

  1. items가 Published 프로퍼티이기 때문에 이 할당은 objectWillChange 이벤트를 트리거 함
  2. objectWillChange가 트리거가 되면 items의 스냅샷을 찍음
  3. 곧이어 items의 저장소에 가져온 사진(fetched)이 할당
  4. 다음 런루프의 틱에서 이전 스냅샷과 현재 값을 비교
    1. 값이 다르면 SwiftUI는 Photos에 의존하는 뷰들을 업데이트

참고) objectWillChange 발생, items 업데이트, 런루프 틱은 모두 메인 액터에서 일어나므로 순서가 보장됨

 

WWDC20 “Data Essentials” 세션에 따르면, body에서 뷰가 너무 많은 작업을 할 때 업데이트가 느려질 수 있다고 설명함

이 업데이트 지연은 body의 과도한 일 뿐만 아니라, 메인 액터에서 많은 작업을 하면 뷰 업데이트가 느려질 수 있음

https://developer.apple.com/videos/play/wwdc2020/10040/

 


Blocking the run loop (7:24)

예를 들어, fetchPhotos 메서드가 오래 걸리는 작업이라 가정한다면, 네트워크 작업을 진행하느라 메인 액터가 블로킹 되어 런루프의 한 틱을 놓치게 됨

→ 이를 hitch(UI 끊김)로 봄

 

 

Poor use of dispatch queues

Swift Concurrency 이전에는 Dispatch를 사용하여 오래 걸리는 작업은 메인 스레드가 아닌 다른 스레드로 보냈었음

그러나 위 코드는 문제가 있다

  • 메인 액터가 아닌 곳에서 ObservableObject를 변경하여 런 루프의 틱이 섞일 수 있음
  • 예를 들어, items에 이미지를 할당하기 시작해 objectWillChange가 호출되어 스냅샷을 찍는 시점이, 런루프의 틱 바로 직전일 경우, 상태 변경 전에 스냅샷을 비교할 가능성이 있음
  • 실제 상태 변경은 런루프 틱 이후에 일어나지만 SwiftUI는 그 변경을 보지 못해 업데이트가 되지 않을 수 있음

→ 올바르게 업데이트 되려면 objectWillChange, ObservableObject의 상태 업데이트, 런 루프의 틱이 순차적으로 진행되어야 함

→ 이것은 모두 메인 액터에서 일어나면 순서를 보장할 수 있음

 

await을 사용하면 훨씬 쉬워짐

메인 액터에서 비동기 호출을 await으로 하게 되면 비동기 작업이 진행되는 동안 메인 액터에서 다른 작업을 할 수 있게 됨

→ 이를 메인 액터를 양보(yield) 한다고 부름

 

 

  • updateItems에서 await을 사용하면 긴 I/O 작업 동안 메인 액터를 SwiftUI에 돌려주어 런루프가 계속 Tick을 유지하고 UI 끊김을 피할 수 있음
  • 비동기 작업이 완료되면 다신 메인 액터 상에서 updateItems에 재진입하므로 안전하게 상태 업데이트 가능

 

FetchPhoto (10:04)

  • fetchPhotos 메서드 구현을 위해 단일 사진을 가져오는 메서드
  • URL을 받아 SpacePhoto를 반환

 

 

FetchPhotos

  • 일부 날짜를 무작위로 골라 특정 날짜를 가져와 URL을 받음

 

 

 

@MainActor

  • @MainActor 어노테이션을 추가하면 컴파일로는 Photos의 프로퍼티와 메서드들이 오직 메인 액터에서만 접근되도록 보장함

 

 

 

CatalogView (14:09)

  • Catalog가 보여질 때마다 updateItems 호출
    • 과거에는 onAppear를 사용했지만, task를 사용하면 비동기 작업 가능
  • task는 뷰의 생명 주기 시작 시점에 시작되어 뷰의 수명이 끝나면 task는 자동으로 취소됨 → •Demystify SwiftUI 세션 참고

 


AsyncImage

  • 새로운 AsyncImage API를 사용하면 원격 서버에서 이미지를 로드하는 것이 쉬워짐
  • 이미지 URL을 AsyncImage로 전달하면 끝
  • resizable, aspectRatio 설정
struct PhotoView: View {
    var photo: SpacePhoto

    var body: some View {
        ZStack(alignment: .bottom) {
            AsyncImage(url: photo.url) { image in
                image
                    .resizable()
                    .aspectRatio(contentMode: .fill)
            } placeholder: {
                ProgressView()
            }
            .frame(minWidth: 0, minHeight: 400)

            HStack {
                Text(photo.title)
                Spacer()
                SavePhotoButton(photo: photo)
            }
            .padding()
            .background(.thinMaterial)
        }
        .background(.thickMaterial)
        .mask(RoundedRectangle(cornerRadius: 16))
        .padding(.bottom, 8)
    }
}

 

 

Save Button

  • SwiftUI에서 버튼 액션은 동기적으로 실행되지만, save 메서드는 비동기
  • 따라서 async task 사용
  • 저장하는 동안 진행 표시를 위해 @State 프로퍼티 추가
  • Save 라벨의 opacity, overlay 설정

 

Refreshable (Modifier)

  • 이 콘텐츠가 새로 고침이 가능한 것을 SwiftUI에 알리는 비동기 클로저
  • 당겨서 새로고침

 

 

결론

  • Swift 5.5 동시성 기능은 SwiftUI와 자연스럽게 맞물림
  • 핵심 요약:
    • 데이터 모델에 async/await 적용
    • main actor로 UI 안정성 확보
    • SwiftUI의 새로운 API (task, refreshable) 적극 활용

“SwiftUI + 새로운 동시성 도구 = 안전하고 직관적인 코드 작성 가능”

728x90