iOS/Swift Concurrency

[스터디] swift6로 마이그레이션 할 때 고려할 점

Hamp 2025. 9. 5. 23:14
반응형

👋 들어가기 전

 

이번 포스팅은 3번 째, 스터디에서 시작에서 소개된 내용이다.

Concurrency 이전에 사용하고 있던 코드를 마이그레이션 할 때, 도움되는 중요한 얘기를 소개해주셨다.

 

한번 천천히 살펴보자.


🏁 학습할 내용

  • Continuation
  • Combine과 함께 사용하기
  • strict Concurrency Checking

♻️ Continuation

 

📋 정의

 

동기코드와 비동기코드를 연결하는 메커니즘

📂 종류

 

여기서 잠깐, 정의 부분에서 , logging correctness violations 부분이 2종류를 나눈다.

 

1. Checked Continuation

  • logging correctness violatinos를 진행하는 Continuation, 규칙을 어겼을 때 디버깅 정보를 알려줌
  • 대신 검사 오버헤드가 존재

 

2. Unsafe Continuation

  • 런타임에 검사를 하지 않아, 오버헤드는 없지만 디버깅이 매우 힘듬
  • 확실한 디버깅이후, Checked에서 Unsafe로 바꾸는 것을 권장

 

사실  Throwing 여부까지 따지면 , 정확한 종류는 4가지다.

 

🤖 예제 코드 

 // 콜백 기반 API (비동기처럼 동작하는 것처럼 흉내냄)
  func fakeAsyncAPI(completion: @escaping (String) -> Void) {
      DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
          completion("✅ Success from callback")
          completion("⚠️ Called again!") // 일부러 두 번 호출
      }
  }

  // MARK: - Checked Continuation
  func testCheckedContinuation() async {
      print("=== withCheckedContinuation ===")
      await withCheckedContinuation { continuation in
          fakeAsyncAPI { result in
              continuation.resume(returning: result) // 첫 번째 resume 정상
              continuation.resume(returning: result)
              // ❌ _Concurrency/CheckedContinuation.swift:169: Fatal error: SWIFT TASK CONTINUATION MISUSE: testCheckedContinuation() tried to resume its continuation more than once, returning
          }
      }
  }

  // MARK: - Unsafe Continuation
  func testUnsafeContinuation() async {
      print("=== withUnsafeContinuation ===")
      await withUnsafeContinuation { continuation in
          fakeAsyncAPI { result in
              continuation.resume(returning: result) // 첫 번째 resume 정상
              continuation.resume(returning: result)
              // 두 번째 resume 시도 시 → 아무런 경고 없음 (그냥 실행됨, crash 가능)
          }
      }
  }

 

🏃 Continuation을 재개하는 방법

 ~withXXXContinuation을 이용해서 Continuation을 생성하면, 그곳은 중단 지점이된다.

 

이때 재개(resume)하는 방법은 크게 3가지다.

  • returnnig: 성공적으로 값을 리턴하거나
  • error: 에러를 던지거나 (Throwing을 붙혀있을 때)
  • result: Result 타입으로 던지거나

 

여기서 주의할 점은, 위에서 봤던 것처럼 2번 이상의 resume이 발생하면 crash가 발생하고,

반대로 resume을 까먹으면 영원히 멈춰있게된다.

 

Continuation은 최대한 갇힌 스코프 내에서 사용해야, 추적 및 디버깅이 쉽다.


🌊 Combine과 함께 사용하기

스터디 장님과 함께 퇴근하면서, Combine과 Concurrency를 같이 사용하는 구조에 대해, 얘기한 적이 있는데

 

그때, 최대한 같이 안쓰는게 좋다고 말씀하신 이유를 납득했다.

 

기본적으로 Combine Operator 클로저 안에서 비동기 작업을 호출하는게 좋은 구조가 아니다.

 

1️⃣ FlatMAp + Future 같이쓰기

 

이벤트가 들어왔고, API 콜을 친 후, 그 결과를 아래로 내려보내고 싶을 때

 

어떻게 할 수 있을까 ??

 

가장 대표적인 map 안에서 호출했다고 가정해보자.

 

result는 string을 받지 않고, Task<Stirng, Never> 형태로 받는다. 실패 ❌

 

제목인 FlatMap과 Future를 써보자.

    subject
      .flatMap({ _ in
        Future<String, Never> { completion in
          Task {
            let result = try await self.asyncFunc() // return String
            completion(.success(result))
          }
        }
      })
      .sink { result in
        result // String
      }
      .store(in: &cancellables)

 

result 변수로 String이 재대로 들어온다. ✅

 

둘의 차이는 뭘까?

 

바로 flatMap Operator는 동기적으로 new publisher를 만들어내기 때문에 다음 파이프 라인으로 넘겨줄 수 있고

map은 단순 변환만 하기 떄문에, 비동기의 결과를 전달할 수 없다.

 

 

2️⃣  values (iOS15이상)

 

Publisher를 AsyncSequence로 바꿔주는 프로퍼티

 

 

Combine을 Concurrency 방향으로 변환할 때, 유용한 키워드

 

    Task {
        for await _ in subject.values {
            let result = try await self.asyncFunc()
            print(result)
        }
    }

🕶️ strict Concurrency Checking

동시성 관련 코드에서 발생할 수 있는 잠재적 문제를 컴파일러가 더 엄격하게 검사하는 설정을 말한다.

 

🎯 목적

  • Swift 동시성 모델을 안전하게 사용하도록 강제
  • 데이터 레이스(data race), 메모리 안전성 문제를 미리 방지

 

⚙️ 설정

설정 Level읜 크게 3가지다.

  • Minimal: @Sendable 정도의 경고를 최소한으로
  • Targeted: 지금 코드베이스에서 경고/에러가 폭발하지는 않음, 다만 동시성 코드에서 Sendable, actor 관련 안정성문제를 미리 검사
  • Complete: 전체 코드에서 동시성 문제 될 수 있는 가능성까지 전부 검사

출처

https://www.donnywals.com/enabling-concurrency-warnings-in-xcode/

 

Enabling Concurrency warnings in Xcode 16 – Donny Wals

If you want to make sure that your code adopts Swift concurrency as correctly as possible in Swift 5.x, it’s a good idea to enable the Strict Concurrency Checking () flag in your project. To do this…

www.donnywals.com

 

반응형