-
[Swift6] @retroactive 를 써야 할까? Extension declares a conformance of imported type ~ 에 대하여Swift 2025. 4. 18. 10:04반응형
Swift6로 올라오면서 extension 해주는 부분에서 아래와 같은 워닝이 발생합니다.
Extension declares a conformance of imported type '~A~' to imported protocol '~B~'; this will not behave correctly if the owners of 'Entity' introduce this conformance in the future
이 워닝엔 보통 Fix가 달려있어 자동으로 @retroactive 를 추가해주면 워닝은 해결됩니다.
하지만 @unchecked Sendable 같이 뭔가 좀 거부감이 들어 자세히 알아보려고 합니다.Add '@retroactive' to silence this warning
@retroactive
Swift6 에서 새롭게 제안된 문법입니다.
영어가 어렵습니다.. 한글 뜻을 찾아보면 '소급적' 이라는 뜻이라고 나옵니다.
한글도 어렵습니다.. 사전을 찾아보면 '지나간 일에까지 거슬러 올라가서 미치게 하는 것.' 이라고 나옵니다.이제 조금 느낌이 오려고 합니다. 워닝 상황을 다시 생각해보면
'이미 만들어져 있는 타입에 extension으로 새로운 프로토콜을 확장시키려고 할 때' 입니다.
사전 뜻과 같이 한번 더 풀어서 보면,
이미 만들어진 타입에 (지나간 일)
extension으로 새로운 프로토콜을 확장시키려고 할 때, 기존 프로토콜에 영향을 미치게 됩니다. (거슬러 올라가서 미치게 하는 것)여기서 '이미 만들어진 타입' 이란 내가 만든 타입이 아닌 외부 모듈에서 정의된 타입을 생각하면 됩니다.
자체 모듈이나 오픈소스 같은 게 될 수도 있고 대표적으로 Swift에 정의되어 있는 String, Int 같은 타입들도 다 해당 됩니다.도입
제안된 0364-retroactive-conformance-warning 를 보면 도입 배경을 보면 아래와 같습니다.
Introduction
Many Swift libraries vend currency protocols, like Equatable, Hashable, Codable, among others, that unlock worlds of common functionality for types that conform to them. Sometimes, if a type from another module does not conform to a common currency protocols, developers will declare a conformance of that type to that protocol within their module. However, protocol conformances are globally unique within a process in the Swift runtime, and if multiple modules declare the same conformance, it can cause major problems for library clients and hinder the ability to evolve libraries over time.동일한 타입에 타입에 특정 protocol을 적용하는 protocol conformances는 Swift 런타임에서 하나로 유니크 해야하는데,
여러 모듈에서 같은 protocl conformances가 생길 수 있다는 겁니다.동일한 타입 + 프로토콜 조합 = protocol conformances
여러 모듈에서 동일한 protocol conformances 를 만들면 충돌이 생길 수 있다!구체적으로 상황을 생각해보면, 아래와 같이 같은 protocl conformances 충돌이 생길 수 있을 때 문제가 될 수 있습니다.
// 의존 관계 C -> B -> A // A Module protocol MyProtocol { } // B Module import A extension String: MyProtocol { } // C Module import B extension String: MyProtocol { }
사실 이렇게 의존 관계가 명확하다면 컴파일러가 잡아줄 수 있습니다.
아래처럼 모듈 의존 관계가 더 복잡해지면 문제가 됩니다.┌─────┐ │ A │ ←────────────┐ └─────┘ │ ▲ │ ┌─────┴─────┐ ┌─┴─┐ │ B │ │ D │ └─────▲─────┘ └───┘ │ ▲ ┌─────┴─────┐ ┌─┴─┐ │ C │──────────┘ │ └───────────┘ │ ▲ │ └───── App ◀───────┘
C, D 에서 중복으로 protocl conformances 가 만들어지면 충돌이 발생합니다.
이런 경우 왜 컴파일러가 못 잡는지 궁금해서 GPT에게 물어보니 아래와 같은 답변을 받았습니다.
딥하게는 저도 공부가 더 필요해서 이번에 하나하나 검증은 못해봤지만 제법 합리적인 설명으로 느껴졌습니다.🔍 왜 컴파일러가 항상 못 막을까?
Swift는 모듈 단위로 conformance metadata를 생성하고,
이걸 런타임에서 합쳐서 “타입 테이블”을 완성합니다.하지만:
- 각 모듈은 자신이 중복 정의하는지 "전체 앱 기준으로는 알 수 없음"
- Swift 컴파일러는 링크될 전체 그래프를 알지 못한 채 모듈별로 빌드하기 때문입니다
단계결과
swiftc로 각각 컴파일 ✅ 별 문제 없이 컴파일됨 앱 최종 링크 시 ❌ 링커가 경고 or 무시 (정확히는 정의 충돌 발생 가능) 앱 실행 시 ❌ 런타임에서 undefined behavior 또는 크래시 그래서 Swift 6에서 @retroactive를 도입하여 “의도된 적합성만 허용하자” 는 방향으로 바뀐 것입니다.
해결?
왜 도입되었고, 언제 써야하는지는 이제 조금 알 것 같습니다.
그렇다면 @retroactive를 만들면 어떻게 되는 걸까요? 해결이 되는 걸까요?답은 그렇지 않다 입니다.
retroactive는 Sendable 처럼 단순히 컴파일 타임에서 의도를 더 명확히 하고 코드 충돌 가능성을 줄이는 데 도움을 줄 뿐, 어떤 기능을 하진 않습니다. 위에 말한 문제 상황은 여전히 발생할 수 있고, @retroactive를 썼다면 문제에 대한 책임도 개발자에게 있습니다.
결론?
지금까지 개인적으로는 @retroactive 보다 모듈을 명시해주는 게 더 낫다고 생각이 들었는데요.
저희 프로젝트에서 조금 테스트를 해보니, @retroactive를 쓰던 모듈을 명시하던 결국은 웬만하면 컴파일러가 잡아주긴 하더라구요.이번에 공부를 해보니 @retroactive를 쓰는 게 어디서 적합성 이슈가 생길 여지가 있을지?
나중에 검색해서도 파악하기가 더 좋으니 이걸 쓰는게 더 좋을까 싶은 생각도 듭니다.애초부터 여러 곳에 중복해서 생길 일은 없어야 하고 필요하다면 한 곳에 모아서 처리를 해야겠지요 🤔
감사합니다.
반응형'Swift' 카테고리의 다른 글
RxSwift Single Swift6 대응하기 (0) 2025.04.11 Swift Seqeunce 이해하기 (0) 2025.03.27 [Swift] TaskPriority, Task 우선순위 동작 이해하기 (0) 2025.03.21 [Swift] Sendable 이해하기 (0) 2025.02.11 [Swift] Actor 이해하기 (0) 2025.02.11