-
Consume noncopyable types in Swift (feat. WWDC 2024)Swift 2024. 11. 18. 18:58
안녕하세요. 그린입니다 🍏
이번 포스팅은 WWDC 2024의 Consume noncopyable types in Swift이라는 세션을 정리해보겠습니다 🙋🏻
세션 주제만 보면 Swift에서 복사할 수 없는 유형 소비하기라고 해석해볼 수 있어요 😃
그럼, Swift에서 복사가 뭐고 언제 복사 불가능한 유형을 소비해야하는지 그리고 값 소유권을 통해 의도를 명확히 어떻게 표현할 수 있는지 등을 집중적으로 다뤄보시죠 🏃🏻
Consume noncopyable types in Swift
최근 Swift에서 복사할 수 없는 유형을 도입했습니다.
자세히 알아보기 전 복사란 무엇인지부터 알아보시죠!
Copying
해당 플레이어라는 구조체를 가진 인스턴스가 있고 그 인스턴스를 player2에 할당하면 복사가 일어납니다.
그러나 우리는 player2의 아이콘을 변경해도 player1의 아이콘은 변경되지 않는다는것을 알고 있죠.
값 타입이니까요 😃
반대로 참조 타입의 경우는 다르죠.
인스턴스 생성 시 해당 데이터를 저장하기 위한 객체가 별도 할당이 되죠.
즉, player1의 컨텐츠는 해당 객체에 대한 자동 관리 참조가 됩니다.
그러고 player2를 동일하게 할당했을때 player1과 동일하기에 참조가 복사됩니다.
이를 얕은 복사라고 합니다.
그렇기에, 두 객체 모두 동일한 개체를 참조하기에 변경하면 같이 반영되죠.
여기까지는 값과 참조 타입을 알고 있다면 너무 당연하고 기초적으로 알고 있죠.
그런데 아래와 같이 클래스에서도 값 타입처럼 복사를 할 수 있어요.
바로 이렇게, 이니셜라이저를 통해 Icon이라는걸 참조 공유하지 않도록 할 수 있습니다.
현재 두 인스턴스는 동일 개체를 참조하고 있죠.
그런데, player2를 보면 자체적으로 이니셜라이저를 호출해 전체 복사본을 만들고 있습니다.
다른 변수가 영향을 받지 않도록 보장하는 별도이지만 동일한 개체를 할당해줘요.
이것이 바로 Copy-On-Write, COW의 핵심이죠.
독립성을 제공하기에 값 유형과 동일한 동작을 얻어요.
깊은 복사 개념인거죠.
Copyable 프로토콜을 이용해도 됩니다.
모든 박스형 프로토콜 타입은 Copyable로 자동 구성이 됩니다.
사실 이렇게 직접 Copyable을 작성하지 않아도 됩니다.
Swift는 Copyable 타입으로 작업하는것이 더 쉽기에 이미 디폴트로 구현되어 있어요.
우선, 여기서보다 더 자세히 알고 싶다면 아래 포스팅을 참고하면 얕은 복사와 깊은 복사에 대해 더 잘 파악하실 수 있어요 😃
값을 복사하는 기능이 유형에 적합한 기본값인 경우는 많지만 어떤 상황에서는 타입을 복사할 수 없는것이 더 이로울때도 있죠.
이제 오늘의 주제를 본격적으로 알아봅시다 😃
Noncopyable types
Copyable은 복사를 제공해주는 프로토콜이였죠.
반대로 ~Copyable로 표현한다면 Copyable에 대한 기본 준수를 억제하는 의미입니다.
즉, ~Copyable을 채택하는 타입은 복사할 수 없는 타입이 됩니다.
만약 이렇게 backup에 system을 복사하려고 한다면 어떻게 될까요?
복사는 지원되지 않기에 Swift가 대신 복사를 사용해요.
이렇게 명시적으로 consume 키워드를 사용해도 되지만 기본적으로 없어도 제공됩니다.
해당 변수를 사용하면 해당 값이 사용되며 해당 변수는 초기화되지 않은 상태로 유지됩니다.
consume이 system의 컨텐츠를 backup으로 이동시켜주는것이죠.
여기서 주의점은 load(system)을 하게되면 오류가 납니다.
이동되어서 아무것도 없기 때문이죠!
즉, system을 소비했다는 의미입니다.
만약 복사할 수 없는 매개변수의 경우 복사할 수 없기에 함수가 값에 대해 갖는 소유권을 선언해줘야 합니다.
즉, format 메서드를 보면 인자를 복사해서 기본적으로 받아오는데 이렇게는 못한다는 소리죠!
consuming을 붙여 호출자로부터 인수를 가져간다는 의미를 나타내야해요.
그러나 여기서는 FloppyDisk가 반환되지 않기에 consuming을 사용하는게 문제가 될 수 있어요.
아무것도 반환되는것이 없으니까 return result에서는 문제를 일으키죠.
포맷에서는 보통 일시적인 액세스만 필요할거에요.
그렇기에 borrowing이라는 키워드를 사용할 수 있습니다.
일시적으로 접근해서 빌리는 개념이죠.
let 바인딩처럼 읽기에 대한 액세스를 제공해줍니다.
그런데 여기서는 disk에 대해 사용하거나 대여해줄 수 없어요.
이렇게 disk는 빌려준것이지 소비할 수 없다고 나타납니다.
다른 방법으로 inout이 있습니다.
이렇게 사용하면 해결할 수 있죠.
이렇게 noncopyable을 활용하면 Swift는 동일한 조건에서 실행이 겹치거나 하는 등의 여러 에러 케이스를 보호해줄 수 있습니다.
복사할 수 없는 타입은 때로 프로그램의 정확성을 향상시켜줍니다.
그렇기에 모든 곳에서 사용하고 싶은 니즈에 따라 Swift 6에서는 복사할 수 없는 제네릭을 사용할 수 있습니다!
Generics
Swift의 기존 제네릭 모델을 기반합니다.
제네릭의 핵심 아이디어는 적합성 제약 조건이 제네릭 유형을 설명한다는것입니다
일반적으로 이렇게 제네릭 타입에도 Copyable을 준수하고 있죠.
이렇게 T에 허용되는 타입의 공간을 제한할 수 있습니다.
이렇게 되면 String에는 Runnable을 준수하는게 없기에 String은 제외가 되죠.
Swift 5.9 이후부터는 Copyable을 준수하지 않는 타입이 있습니다.
이렇게 대부분 우리가 아는 타입은 Copyable을 준수하죠.
근데 BankTransfer 타입은 준수하지 않습니다.
만약 더 넓게 본다면 아래처럼 특정 유형이 Copyable을 준수한다고 항상 가정할 수 없어요.
이렇게 Runnable에 ~Copyable을 붙여 복사 할 수 없는걸 나타내게 합니다.
이렇게 되면 계층 구조가 변경되기에 Copyable 공간은 Runnable을 포함하는게 아니라 겹쳐지죠.
여기서 Command는 복사가 가능하기에 겹치는 부분에 위치하죠.
Runnable 프로토콜을 사용해 BankTransfer를 확장하면 해당 지점이 복사되는게 아니라 Runnable 내에서 이동합니다.
해당 형태를 보면 T는 여전히 Copyable 합니다.
그렇기에 실행이 가능하고 복사가 가능한 Command와 같은 타입만의 실행을 허용해요.
하지만 이렇게 ~Copyable도 준수하도록 채택하면 허용되는 타입이 실행 가능한 모든 타입으로 확대됩니다.
즉, T가 복사 가능하지 않을 수 있음도 내포하죠.
~Copyable을 통해 덜 구체적으로 제약 조건을 확장할 수 있죠.
또한 이렇게 타입에 제네릭을 사용한다해도 해당 타입은 여전히 Copyable하기에 이렇게 데이터 사용을 위해 ~Copyable을 적용해줘야 복사할 수 없도록 만들어집니다.
Action이 Copyable 타입이 나타나는걸 막지 않기에 Action에 Command 타입을 계속 넣을 수 있죠.
Job은 Action을 복사할 필요가 없기에 복사할 수 없는 타입도 작동될거라는걸 보장합니다.
그런데 여기서 Action에 입력한 타입이 Copyable이라는걸 안다면 어떻게 될까요?
그런 다음에 Job은 작업을 위한 컨테이너일 뿐이기에 복사할 수 있죠.
그럼 copy job 코드에서 에러가 납니다.
이렇게 확장해 조건부 Copyable을 채택함으로 허용해줄 수 있습니다 😃
즉, 이런 형태를 가지게 되는것이죠.
우리는 복사할 수 없는 일반 매개변수를 사용해 타입을 정의하는 방법을 살펴보았는데요.
마지막으로 해당 타입의 확장을 자세히 살펴보시죠!
Extensions
해당 코드를 보면 Job의 getter를 만들고 싶은데, Command는 복사 가능한 타입이라 action 반환이 됩니다.그렇지만, BankTransfer는 복사 불가능한 타입이라 에러가 나죠.
이것이 모든 확장이 작동하는 방식이죠.
확장 타입 범위에 있는 모든 일반 매개변수는 복사 가능으로 제한됩니다.
여기엔 프로토콜에 Self가 포함되죠.
익스텐션이 이런 방식으로 작동되면 좋은 이점들이 있죠.
Job이 실제로 JobKit이라는 모듈의 일부라고 가정해볼께요.
그 다음 Cancellable 프로토콜이 있구요.
일반적으로 Action은 복사 가능하지 않을 수 있기에 적합성은 복사 가능한 Action에 대한 조건부로 기본적으로 적용됩니다.
그렇기에 Action이 Copyable이면 Job도 마찬가지죠.
즉, Cancellable을 준수하게 됩니다.
그렇기에 해당 Job 타입을 게시할 수 있고 복사 가능한 타입으로만 작업하도록도 사용할 수 있죠.
만약 이 확장을 복사 가능 여부에 상관없이 모든 작업에 적용하려면 어떻게 해야 할까요?
이렇게 확장에선 복사 가능 제약 조건을 해제합니다.
이제 Job은 Action이 Copyable이라고 가정하지 않고 Cancellable을 준수하게 됩니다.
마무리
이렇게 Swift에서 복사가 어떻게 작동하고 어떤 문제를 일으킬 수 있는지부터 알아봤어요.
복사할 수 없는 타입에 대해 적절히 절충한다면 프로그램 정확성을 향상시킬 수 있을것 같아요!
레퍼런스
'Swift' 카테고리의 다른 글
Explore the Swift on Server ecosystem (feat. WWDC 2024) (34) 2024.12.02 ETag 캐싱으로 앱 성능 최적화하기 (32) 2024.11.26 Go further with Swift Testing (feat. WWDC 2024) (4) 2024.09.30 Meet Swift Testing (feat. WWDC 2024) (6) 2024.09.26 Swift 6 - @retroactive (7) 2024.09.12