
준수하는 타입에 대해 수명 주기와 소유권을 제어하는 프로토콜
Swift의 모든 타입(Enum, Class, Struct)는 모두 Copyable을 암시적으로 준수
즉, Class를 제외한 모든 타입은 값이 복사되어 할당되거나 전달됨.
// 명시해주지 않더라도 이미 암시적으로 Copyable을 준수
struct TestStruct {
///
}
// 매개 변수로 들어올 구조체는 복사되어 전달
func justFunc(value: TestStruct) {
///
}
암시적으로 준수되던
Copyable프로토콜을 해제하여 소유권 및 생명주기 관리
Copyable을 해제함으로써 해당 타입은 복사가 불가능해지고 이동만 가능
이동된 객체(소유권이 넘어간 객체)를 가졌던 변수는 재사용 불가.
~Copyable을 준수한 Struct, Enum 타입은 deinit을 가질 수 있게 됨으로써 리소스 해제 시점 관리가 가능
⚠️ Any 타입으로 업캐스팅 불가 (즉, 타입 소거 불가)
⚠️ 제네릭 사용 시, 명시적으로 제약 조건에 추가해주어야 함.
protocol TokenHandlable {
func isValid() -> Bool
mutating func invalidate()
}
struct SecureToken: TokenHandlable, ~Copyable {
private var tokenID: String
private var active: Bool
private let onDeinit: ((String) -> Void)?
init(id: String, onDeinit: ((String) -> Void)? = nil) {
self.tokenID = id
self.active = true
self.onDeinit = onDeinit
print("Token \(id) Created")
}
func isValid() -> Bool {
return active
}
mutating func invalidate() {
self.active = false
print("Token \(tokenID) Invalidated explicitly")
}
// Struct임에도 deinit 사용 가능 (Scope 종료 시 자동 호출)
deinit {
onDeinit?(tokenID)
print("Token \(tokenID) Destroyed from memory")
}
}
final class AuthManager {
// borrowing: 토큰을 확인만 하고 소유권은 가져가지 않음
func verifyToken<T: TokenHandlable & ~Copyable>(_ token: borrowing T) -> Bool {
return token.isValid()
}
// consuming: 토큰을 사용하고 폐기함 (소유권 이동)
func consumeToken<T: TokenHandlable & ~Copyable>(_ token: consuming T) {
// 작업을 수행...
if token.isValid() {
print("Action performed with token.")
}
// 함수 범위(Scope)가 끝나면 token의 deinit이 호출
}
}
borrowing, consuming 키워드를 활용해 소유권에 대한 제어 가능
https://velog.io/@js1436kt/Swift-%EC%86%8C%EC%9C%A0%EA%B6%8C-%ED%82%A4%EC%9B%8C%EB%93%9CCopyable
Class에서 사용되던 참조 관리 모델인 ARC와 값 타입에 복사를 불가능하게 함으로써 생기는 원본에 대한 추적 관리가 필요해짐.
| 특징 | Class (Reference Counting) | ~Copyable Struct (Ownership) |
|---|---|---|
| 메모리 관리 주체 | Runtime (실행 중 관리) | Compiler (빌드 타임 확정) |
| 비용 (Overhead) | Retain/Release 시 Atomic 연산 발생 (성능 비용 O) | Zero Cost (단순 이동 명령만 수행) |
| 변수 할당 동작 | 참조 복사 (RC 증가, 인스턴스 공유) | 소유권 이동 (이전 변수 무효화) |
| 메모리 해제 시점 | 모든 참조가 사라질 때 (비결정적일 수 있음) | 소유권이 소멸되는 즉시 (Scope 종료 or consume) |
| 주요 버그 | 순환 참조 (Retain Cycle), Race Condition | Use After Free (컴파일러가 사전 차단) |
| 사용처 | 상태 공유가 필요한 뷰 모델, 서비스 객체 | 고성능 시스템, 파일 핸들, Mutex, 대용량 버퍼 |
~Copyable을 사용함으로써 값 타입을 활용해 Class 타입 대체가 가능해짐으로써 성능적으로 향상 기대
소유권의 이동이 명확한 로직에서 성능 향상 기대 가능, ViewModel과 같이 여러 곳에서 소유권이 필요한 경우는 여전히 Class 사용
하지만 코드 작성자 입장에서 소유권을 추적하면서 작성해야 해서 번거로움 발생 but, 유지보수 비용이 절감(에러 처리 등에 대한 비용 감소)
이전에 작성한 region-based-isolation 내용 중, 데이터 경쟁이 발생할 수 있는 부분을 판단해서 에러를 띄움을 알 수 있었음.
이 때, ~Copyable을 활용해 소유권을 넘기면서 작성된 함수 등이 컴파일러가 읽으면, 소유권이 아예 넘어가니까 데이터 경쟁이 없음을 예상하고 에러를 띄우지 X
즉, 컴파일러의 Sendable 검증이 없더라도 수학적으로 데이터 경쟁이 성립되지 않기에 가능.
https://github.com/swiftlang/swift-evolution/blob/main/proposals/0429-partial-consumption.md