
어느 평화로운 오후, 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 conformance 문제구나!"
struct AppTokenResponse: Codable, @unchecked Sendable {
let accessToken: String
let refreshToken: String
let expiresIn: Int
let tokenType: String
}
결과: 똑같은 에러 발생 ❌
"혹시 Alamofire 버전 문제?"
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)
}
}
결과: 여전히 똑같은 에러! ❌
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)
}
}
결과: 역시 실패... ❌
이쯤 되니 미칠 것 같았습니다. 😭
모두 실패! 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로 격리된 타입을 다른 스레드에서 사용할 수 없어요!"
라는 에러가 발생한 것입니다! 🤦♂️
1. Target 선택
2. Build Settings
3. 검색: "Default Actor Isolation"
4. MainActor → nonisolated로 변경
5. Clean Build (Shift + Cmd + K)
6. 빌드 ✅
결과: 모든 에러 해결! 🎉
Main actor-isolated conformance of 'AppTokenResponse'
이 메시지가 "AppTokenResponse가 MainActor로 격리되어 있다"고 명확히 말하고 있었는데, 처음엔 놓쳤습니다.
코드만 보지 말고 Build Settings도 확인하는 습관이 필요합니다.
만약 당신도 "Main actor-isolated conformance" 에러를 만났다면:
Build Settings → Default Actor Isolation 확인@MainActor가 직접/간접적으로 적용되었는지 확인5시간의 사투 끝에 배운 건, 가장 간단한 해결책이 가장 좋은 해결책이라는 것입니다.
복잡한 workaround를 찾기보다는: