본문 바로가기
WWDC/2024

Migrate your app to Swift 6 (추천세션!)

by 찜토끼 2024. 8. 8.

https://developer.apple.com/videos/play/wwdc2024/10169/

 

Migrate your app to Swift 6 - WWDC24 - Videos - Apple Developer

Experience Swift 6 migration in action as we update an existing sample app. Learn how to migrate incrementally, module by module, and how...

developer.apple.com

해당 포스트는 위 영상을 기반으로 개인적으로 메모/정리한 글입니다.

개요

기존 코드를 Swift 6 로 Migrate 하면서 컴파일러가 어떻게 data race 위험성을 잡아내는지 알아보자. Data 를 isolation 시키는 테크닉과 공유 mutable state 에 대한 동시 접근을 없애는 테크닉을 배워보자.


Swift 6 로의 Migrate 장점

  • Swift 6는 완전히 data isolation 를 보장하도록 만든다.
  • Data race 때문에 재현이 힘든 크래시를 만난 경험이 있다면 당장 Swift 6 로 코드를 업데이트하라.

 

Migrate 순서

1. Xcode 16 Open

이 상태로도 이미 워닝은 발생한다.

data isolation 에서 문제가 발생할 수 있는 지점을 경고해준다
워닝을 expand 하면 친절하게 해결 방법도 제시해준다

2. Complete checking

2-a. [Target] 의 Build Setting → Strict Concurrency Checking: complete 로 설정

2-b. 빌드 후 컴파일러가 잡아주는 data race 관련 워닝을 수정해주기

참고로, nonisolated(unsafe) 키워드 사용해서 워닝 지우기 가능(lint disable 같은 느낌).

이 부분은 concurrency checking 을 패스한다고 알려주는 것. 개발자가 책임지겠다.

3. enable Swift 6 Mode

[Target] 의 Build Setting → Swift Language Mode → Swift 6

 

Migration Example

대부분은 워닝에서 해결책을 제시해준다.

 

Ex1) Shared mutable state in global variables

글로벌 변수의 shared mutable state 예제

웬만하면 let 으로 만드세요 아니면 @MainActor 로 만드세요.

Q. let 이라도 lazy init 시점에서 두 개의 스레드가 동시 접근할 수 있지 않느냐?
A. 자동으로 atomic 하게 동작 됨.

 

Ex2) Shared mutable state in global instances and functions

글로벌 함수에서의 shared mutable state 예제

이 글로벌 함수를 MainActor로 만들 수 있다.

 

Delegate callbacks 과 동시성

딜리게이트의 콜백이 어느 스레드에서 호출되는지는 동시성 환경에서 중요한 문제임.

대부분의 최신 View 관련 프레임워크는 딜리게이트 콜백을 전부 메인스레드에서 호출해준다. (애초에 MainActor 로 정의됨)

그러나 어떤 것들은 메인스레드에서 불린다는 보장이 안 된다. (ex: Apple의 HealthKit) 이 경우는 딜리게이트를 구현하는 쪽에서 올바른 큐에서 콜백 작업이 보장되도록 신경을 써야 함. 개발자에게 짐이 되는 작업이다. → Swift Concurrency 에서 `이것을 보장하거나, 보장하지 않는다`는 것을 명시적으로 나타내준다.

- 콜백에 대한 명세가 없는 경우: non-isolated 로 간주되며 isolation 이 필요한 데이터에 접근 못 하게 함.

- 콜백이 isolation 보장을 명시하는 경우: 콜백을 받는 쪽에서는 이 보장을 신뢰할 수 있음

public protocol CoffeineThresholdDelegate: AnyObject { 
    @MainActor func caffeineLevel(at level: Double)
}

(ex: 항상 main actor에서 콜백을 호출하겠다)

 

Data between actors

main actor 에서 다른 actor 로 데이터를 넘길 때, data race 가 일어날 수도 있다고 경고해준다

currentDrinks(Drink Struct)가 사실상 sendable 타입이라도, 이것이 다른 모듈의 Public type 이라면 Swift 는 자동으로 sendability 를 추론해주지 않는다.

만약 해당 모듈이 Sendable 을 명시해준다면 워닝이 해결된다

만약 위처럼 Sendable 을 명시할 수 없는 타입이라면? (ex: NSObject 등)

nonisolated(unsafe) 키워드를 사용할 수 있다. 이때는 개발자가 알아서 보장해주기.

 

내가 소유하지 않은 코드를 다룰 때

MainActor.assumeIsolated: Swift 컴파일러에게 ‘이건 main actor에서 실행되고 있다’고 알려주는 것. (main actor 에서 새로운 task 를 시작시키는 게 아님을 주의)

CLLocationManager delegate 가 항상 Main thread 에서 실행된다는 것을 확신하고 있을 때

main actor 가 아닌 곳에서 이 코드가 불릴 때는 trap 이 발생해서 실행 중에 프로그램이 멈춘다. (마치 assert 의 역할을 함) → data race condition 보다는 trap 으로 알려주는 게 낫다는 판단

shorthand: @preconcurrency

 

Common patterns and an incremental strategy

Migration 을 시작하면 엄청나게 많은 워닝이 처음엔 발생할 수 있다. 패닉하지 말 것. 아래 매뉴얼을 따라 차근차근 수정해나가면 된다.

  1. 간단하게 해결 가능한 이슈들을 먼저 해결하기 → main actor, immutable 등
  2. 많은 이슈들의 근원이 되는 원인을 찾아보기 → 한 줄 수정으로 해결되는 여러 개의 워닝들
  3. 최신 SDK, 최신 Xcode 를 적용하기 → 수정을 도와줄 어노테이션들이 적용된다.
  4. Take your time → 무리해서 급하게 Migrate 하지 않아도 된다.