Swift 6 Concurrency 에러와의 5시간 사투 - "Main actor-isolated conformance" 해결기

RYEOL·2026년 1월 15일

Swift

목록 보기
15/15
post-thumbnail

🔥 문제의 시작

어느 평화로운 오후, Xcode에서 빌드를 돌렸다가 이런 에러를 만났습니다.

/AuthInterceptor.swift:53:14
Main actor-isolated conformance of 'AppTokenResponse' to 'Decodable'
cannot satisfy conformance requirement for a 'Sendable' type parameter

"뭐야 이게? 어제까지 잘 되던 코드인데?" 🤔

📝 문제 상황

// AuthInterceptor.swift - 토큰 갱신 로직
AF.request(refreshUrl, method: .post, parameters: parameters, encoding: JSONEncoding.default)
    .validate()
    .responseDecodable(of: AppTokenResponse.self) { [weak self] response in
        // ⬆️ 여기서 에러 발생!
        switch response.result {
        case .success(let data):
            self?.tokenManager.save(accessToken: data.accessToken, refreshToken: data.refreshToken)
            completion(.retry)
        case .failure:
            self?.tokenManager.clearTokens()
            completion(.doNotRetry)
        }
    }
// 완벽해 보이는 DTO
struct AppTokenResponse: Codable, Sendable {
    let accessToken: String
    let refreshToken: String
    let expiresIn: Int
    let tokenType: String
}

코드는 완벽해 보이는데 대체 뭐가 문제일까요?


🔍 첫 번째 시도: Sendable이 문제인가?

시도 1: @unchecked Sendable 추가

"아, Sendable conformance 문제구나!"

struct AppTokenResponse: Codable, @unchecked Sendable {
    let accessToken: String
    let refreshToken: String
    let expiresIn: Int
    let tokenType: String
}

결과: 똑같은 에러 발생 ❌


🔍 두 번째 시도: Alamofire가 문제인가?

"혹시 Alamofire 버전 문제?"

시도 2: Async/Await로 변경

Task {
    do {
        let tokenResponse = try await AF.request(
            refreshUrl,
            method: .post,
            parameters: parameters,
            encoding: JSONEncoding.default
        )
        .validate()
        .serializingDecodable(AppTokenResponse.self)
        .value

        tokenManager.save(
            accessToken: tokenResponse.accessToken,
            refreshToken: tokenResponse.refreshToken
        )
        completion(.retry)
    } catch {
        tokenManager.clearTokens()
        completion(.doNotRetry)
    }
}

결과: 여전히 똑같은 에러! ❌


🔍 세 번째 시도: nonisolated init 명시해볼까?

시도 3: nonisolated init 명시

struct AppTokenResponse: Codable, Sendable {
    let accessToken: String
    let refreshToken: String
    let expiresIn: Int
    let tokenType: String

    enum CodingKeys: String, CodingKey {
        case accessToken, refreshToken, expiresIn, tokenType
    }

    nonisolated init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        accessToken = try container.decode(String.self, forKey: .accessToken)
        refreshToken = try container.decode(String.self, forKey: .refreshToken)
        expiresIn = try container.decode(Int.self, forKey: .expiresIn)
        tokenType = try container.decode(String.self, forKey: .tokenType)
    }
}

결과: 역시 실패... ❌

이쯤 되니 미칠 것 같았습니다. 😭


🤯 절망의 순간들

시도한 것들:

  • ✅ Clean Build Folder (Shift + Cmd + K)
  • ✅ DerivedData 삭제
  • ✅ Alamofire 5.9.1로 다운그레이드
  • ✅ Strict Concurrency Checking을 Targeted로 변경
  • ✅ 패키지 제거 후 재설치
  • ✅ Xcode 재시작 (3번이나...)

모두 실패! 5시간째 같은 에러와 싸우고 있었습니다.


💡 진실의 순간

포기하려던 찰나, 한 가지 의문이 들었습니다.

"왜 AppTokenResponse가 MainActor-isolated라고 하는 거지?
나는 @MainActor를 붙인 적이 없는데?"

그리고 Build Settings를 뒤지다가...

🎯 범인을 찾았다!

Target → Build Settings → Swift Compiler - Concurrency
→ Default Actor Isolation = MainActor  ⬅️ 이게 범인!

진짜 문제

이 설정이 켜져 있으면 프로젝트의 모든 타입이 자동으로 @MainActor로 격리됩니다!

// 내가 작성한 코드
struct AppTokenResponse: Codable, Sendable {
    let accessToken: String
    // ...
}

// 컴파일러가 실제로 보는 코드
@MainActor  // ← 자동으로 추가됨!
struct AppTokenResponse: Codable, Sendable {
    let accessToken: String
    // ...
}

그래서 Alamofire가 백그라운드 스레드에서 JSON을 디코딩하려고 할 때:

"MainActor로 격리된 타입을 다른 스레드에서 사용할 수 없어요!"

라는 에러가 발생한 것입니다! 🤦‍♂️


✅ 해결 방법

Default Actor Isolation 제거 (추천)

1. Target 선택
2. Build Settings
3. 검색: "Default Actor Isolation"
4. MainActor → nonisolated로 변경
5. Clean Build (Shift + Cmd + K)
6. 빌드 ✅

결과: 모든 에러 해결! 🎉


🎓 배운 교훈

1. 에러 메시지를 정확히 읽자

Main actor-isolated conformance of 'AppTokenResponse'

이 메시지가 "AppTokenResponse가 MainActor로 격리되어 있다"고 명확히 말하고 있었는데, 처음엔 놓쳤습니다.

2. 프로젝트 설정도 의심하자

코드만 보지 말고 Build Settings도 확인하는 습관이 필요합니다.


🎯 체크리스트: 비슷한 에러를 만났다면?

만약 당신도 "Main actor-isolated conformance" 에러를 만났다면:

  • Build Settings → Default Actor Isolation 확인
  • 해당 타입에 @MainActor가 직접/간접적으로 적용되었는지 확인

마무리

5시간의 사투 끝에 배운 건, 가장 간단한 해결책이 가장 좋은 해결책이라는 것입니다.

복잡한 workaround를 찾기보다는:

  • 프로젝트 설정을 점검하고
  • Swift Concurrency를 올바르게 이해하고
  • 필요한 곳에만 명시적으로 격리를 적용하는 것

References

profile
Flutter, Swift 모바일 개발자의 스타트업에서 살아남기

0개의 댓글