관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS] Swift6 개념 (옵트인, 데이터 레이스 안정성, Default Actor Isolation, Approachable Concurrency) 본문

iOS 응용 (SwiftUI)

[iOS] Swift6 개념 (옵트인, 데이터 레이스 안정성, Default Actor Isolation, Approachable Concurrency)

jake-kim 2026. 9. 14. 00:21

swift6 세팅

  • swift6는 xcode16부터 지원
  • Xcode > targets > Build Settings > swift compiler - Language에서 swift6 선택

swfit6는 옵트인 모드

  • 패키지/모듈 단위로 swift6를 적용할 수 있음
    • 아래처럼 swiftLanguageMode(.v6)으로 적용 가능
  • 모듈별 점진적 마이그레이션이 설계 의도
// Package.swift
.target(name: "MyLib", swiftSettings: [.swiftLanguageMode(.v6)])

데이터 레이스란?

 

  • 두 개 이상의 스레드가 동시에 동일한 메모리 공간에 접근할 때 발생하며, 다음 조건이 모두 충족되는 상태를 의미 
    • 동시성: 둘 이상의 스레드가 동시에 메모리에 접근
    • 동일 메모리: 최소 하나의 작업이 해당 메모리에 값을 쓰는(Write) 작업을 수행
    • 동기화 부재: 뮤텍스(Mutex), 락(Lock), 또는 원자적 연산(Atomic) 같은 동기화 메커니즘이 전혀 없음
  • Swift5에서는 데이터 레이스 위험이 있음

Default Actor Isolation 옵션

  • swift 6.2에서 추가된 기능
  • 코드 대부분이 어차피 메인 스레드에서 돌기 때문에, 타입/함수마다 @MainActor를 일일이 붙여야만 했었음
  • 아래 사진처럼 `MainActor`로 작성하면 @MainActor를 따로 붙이지 않아도 타입/함수/변수가 자동으로 @MainActor인 것처럼 취급됨
    • swift 6에서부터는 데이터레이스를 해치는 코드가 발견되면 Sendable을 붙이라는 컴파일 에러가 나기 때문에 이렇게 설정해준것 (이미 진행중인 프로젝트에 swift6 마이그레이션을 쉽게해주기 위함)

ex)

// Default Actor Isolation = MainActor 일 때
class Counter {              // 암묵적으로 @MainActor
    var n = 0
    func bump() { n += 1 }
}

func test() {                 // 암묵적으로 @MainActor
    let c = Counter()
    Task { c.bump() }         // 메인 액터 상속 → 안전, 에러 없음
}
  • @MainActor라는 것은 동시성을 안쓴다는 의미이고, 만약 동시성이 필요하면 @concurrent를 명시적으로 붙여서 사용
@concurrent로 명시적으로 벗어남

@concurrent
func heavyWork() async {
    // 여기는 메인 액터가 아님, 백그라운드에서 돎
}

Approachable Concurrency 옵션

  • Swift 6.2에서 "6.0이 너무 빡빡하다"는 피드백에 대응해, 관련된 여러 개선(SE-0461 등)을 정식 Swift 7 도입 전에 조기 체험할 수 있게 묶어놓은 것
Approachable Concurrency (Yes)          ← 상위 스위치, 여러 기능의 묶음
    ├─ Default Actor Isolation 선택 가능해짐   ← 그 중 하나
    ├─ nonisolated async 함수가 호출자 격리 상속 (SE-0461)

  • Approachable Concurrency = "이 묶음 기능들을 쓸지 말지" (on/off 스위치, 선택 항목을 노출시키는 게이트)
  • Default Actor Isolation = "그 안에서, 기본 격리값을 뭘로 할지" (MainActor / nonisolated 중 선택)

swfit6 제대로 알아가기

  • swift6는 데이터 레이스 안정성을 위해 컴파일 에러를 활용하는데, 이건 default actor isolation을 nonisolated로 하여, 어떤것들이 컴파일 에러가 나고, 데이터 레이스 안정성을 위해 어떤 코드를 추가해야하는지 알아볼 것
  • Default Actor Isolation을 nonisolated로 설정
  • 아래처럼 코드를 작성하면 컴파일 에러 발생

에러 내용

Sending value of non-Sendable type '() async -> ()' risks causing data races

class Counter {
    var n = 0
    func bump() { n += 1 }
}

let c = Counter()
Task { c.bump() }   // 스레드 A
Task { c.bump() }   // 스레드 B
  • 에러가나는 이유?
    • n += 1은 사실 세 단계: 읽기 → 더하기 → 쓰기.
    • 두 스레드가 동시에 실행하면:
A: n 읽음 (0)
B: n 읽음 (0)      ← A가 아직 안 썼음
A: 1 씀
B: 1 씀            ← A의 작업이 사라짐
  • 결과는 2가 아니라 1. 이게 데이터 레이스
  • 100번 중 99번은 잘 돌고, 하필 사용자 기기에서만 터지며, Swift 5에서는 컴파일러가 이걸 전혀 막아주지 않았음
  • Swift 6는 위 코드를 아예 컴파일 에러로 만들어서 해결
    • 핵심 아이디어: `isolation` (격리 구역)
  • main actor 구역을 만들고, actor 하나당 하나씩만 접근하게 만듦 (그 외 아무데도 속하지 않는 코드는 nonisolated라고 표현)
  • actor 구역 내부는 안전하며, 구역 경계를 넘어갈 때문 위험하니, 넘어가는 값이 안전한지 검사하는 것이 Swift6의 핵심 
    • 이걸 위한 키워드: Sendable / actor / @MainActror

 

Sendable, actor @MainActor에 관한 글은 다음 포스팅 글에서 계속..

Comments