Swift: Copyable, noncopyable

틀틀보·2026년 1월 7일

Swift Concurency

목록 보기
8/11
post-thumbnail

준수하는 타입에 대해 수명 주기와 소유권을 제어하는 프로토콜

기본적으로 준수되는 Copyable

Swift의 모든 타입(Enum, Class, Struct)는 모두 Copyable을 암시적으로 준수

즉, Class를 제외한 모든 타입은 값이 복사되어 할당되거나 전달됨.

// 명시해주지 않더라도 이미 암시적으로 Copyable을 준수
struct TestStruct {
	///
}

// 매개 변수로 들어올 구조체는 복사되어 전달
func justFunc(value: TestStruct) {
	///
}

~Copyable(noncopyable)

암시적으로 준수되던 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이 호출
    }
}

ARC와 소유권 모델

Class에서 사용되던 참조 관리 모델인 ARC와 값 타입에 복사를 불가능하게 함으로써 생기는 원본에 대한 추적 관리가 필요해짐.

특징Class (Reference Counting)~Copyable Struct (Ownership)
메모리 관리 주체Runtime (실행 중 관리)Compiler (빌드 타임 확정)
비용 (Overhead)Retain/Release 시 Atomic 연산 발생 (성능 비용 O)Zero Cost (단순 이동 명령만 수행)
변수 할당 동작참조 복사 (RC 증가, 인스턴스 공유)소유권 이동 (이전 변수 무효화)
메모리 해제 시점모든 참조가 사라질 때 (비결정적일 수 있음)소유권이 소멸되는 즉시 (Scope 종료 or consume)
주요 버그순환 참조 (Retain Cycle), Race ConditionUse After Free (컴파일러가 사전 차단)
사용처상태 공유가 필요한 뷰 모델, 서비스 객체고성능 시스템, 파일 핸들, Mutex, 대용량 버퍼
  • ~Copyable을 사용함으로써 값 타입을 활용해 Class 타입 대체가 가능해짐으로써 성능적으로 향상 기대

  • 소유권의 이동이 명확한 로직에서 성능 향상 기대 가능, ViewModel과 같이 여러 곳에서 소유권이 필요한 경우는 여전히 Class 사용

  • 하지만 코드 작성자 입장에서 소유권을 추적하면서 작성해야 해서 번거로움 발생 but, 유지보수 비용이 절감(에러 처리 등에 대한 비용 감소)

region-based-isolation

이전에 작성한 region-based-isolation 내용 중, 데이터 경쟁이 발생할 수 있는 부분을 판단해서 에러를 띄움을 알 수 있었음.

이 때, ~Copyable을 활용해 소유권을 넘기면서 작성된 함수 등이 컴파일러가 읽으면, 소유권이 아예 넘어가니까 데이터 경쟁이 없음을 예상하고 에러를 띄우지 X

즉, 컴파일러의 Sendable 검증이 없더라도 수학적으로 데이터 경쟁이 성립되지 않기에 가능.

참고
https://github.com/swiftlang/swift-evolution/blob/main/proposals/0390-noncopyable-structs-and-enums.md

https://github.com/swiftlang/swift-evolution/blob/main/proposals/0377-parameter-ownership-modifiers.md

https://github.com/swiftlang/swift-evolution/blob/main/proposals/0429-partial-consumption.md

https://developer.apple.com/videos/play/wwdc2024/10170/

profile
안녕하세요! iOS 개발자입니다!

0개의 댓글