프로젝트에서 TokenManager를 Repository에서 주입받아 토큰을 저장하는 과정에서,
아래와 같은 컴파일 에러를 마주쳤다.
Cannot assign to property: 'tokenManager' is a 'let' constant
LoginRepositoryImpl에서는 TokenManagerProtocol을 의존성으로 주입받아 사용하고 있었다.
final class LoginRepositoryImpl: LoginRepositoryProtocol {
private let tokenManager: TokenManagerProtocol
init(tokenManager: TokenManagerProtocol) {
self.tokenManager = tokenManager
}
func save(token: String) {
tokenManager.accessToken = token
}
}
TokenManagerProtocol은 다음과 같이 정의되어 있었다.
protocol TokenManagerProtocol {
var accessToken: String? { get set }
var refreshToken: String? { get set }
}
구현체는 class였지만,
final class TokenManager: TokenManagerProtocol {
var accessToken: String?
var refreshToken: String?
}
tokenManager.accessToken = token 코드에서 컴파일 에러가 발생했다.
참조 타입인 Class가 let 으로 선언되면 참조만 고정되고 내부 값들은 변할 수 있지만,
값 타입인 Struct는 let으로 선언되면 변할 수 없다.
Swift 입장에서 TokenManagerProtocol은
으로 인식되는데, 가장 안전한 규칙을 적용하여
“값 타입일 수도 있으니 내부 프로퍼티 변경을 허용할 수 없다” 라고 판단한다고 한다.
AnyObject 란?
AnyObject는 초기 Swift 1~2 시절, Object-C와의 브릿징을 위해 사용하던 프로토콜이었다.Object-C 에는 다음과 같은 개념들이 있었는데,
- id : “어떤 클래스 인스턴스든 담을 수 있는 포인터” (동적 타입)
- NSObject * : 많은 Cocoa API에서 사실상 “기본 객체 포인터”로 쓰임
Swift가 Cocoa/Objective-C API와 붙으려면, 어떤 Obj-C 객체든 담을 수 있고 런타임에서 동적 호출이 가능한 타입이 필요했다.
그 역할을 Swift 쪽에서 해주는 대표 타입이 AnyObject 라고 한다.지금은 Swift가 발전하면서,
Any가 진짜로 모든 타입 (struct, class, enum 등)을 담는 상위타입이 되었고,AnyObject: 모든 class 타입 이라는 의미가 더 선명해져현재는 class 전용 제약을 표시할 때 많이 사용한다고 한다.
프로토콜에 AnyObject 제약을 추가해 참조 타입 전용 프로토콜임을 명확히 해주면
protocol TokenManagerProtocol: AnyObject {
var accessToken: String? { get set }
var refreshToken: String? { get set }
func clear()
}
이후 동일한 코드에서 에러 없이 정상 작동한다!
private let tokenManager: TokenManagerProtocol
tokenManager.accessToken = accessToken
tokenManager.refreshToken = refreshToken