티스토리 뷰
원문 : https://www.hackingwithswift.com/swift/5.9/noncopyable-structs-and-enums
SE-0390에서는 복사할 수 없는 구조체와 열거형 개념을 도입하여 구조체나 열거형의 단일 인스턴스를 여러 곳에서 공유할 수 있게 되었으며, 궁극적으로 소유자는 여전히 한 명이지만 이제 코드의 여러 부분에서 액세스할 수 있게 되었다.
중요:
이 변경 사항에는 여러 가지 미묘한 점이 있으므로 아래에서 명확히 설명하려고 노력했지만,
몇 가지 사항을 몇 번 읽어야 한다고 해도 놀라지 말자.
첫째, 이 변경 사항에는 요구 사항을 억제하는 새로운 구문인 ~Copyable
이 도입되었다. 이는 "이 유형은 복사할 수 없습니다"라는 의미이며, 이 억제 구문은 현재 다른 곳에서는 사용할 수 없다.(?) 예를 들어 ~Equatable
을 사용하여 유형에 대해 ==
를 선택 해제할 수 없다. 따라서 다음과 같이 복사할 수 없는 새로운 User
구조체를 만들 수 있다:
struct User: ~Copyable {
var name: String
}
참고: 복사할 수 없는 유형은 Sendable
이외의 프로토콜을 준수할 수 없다.
User
인스턴스를 생성하면 복사할 수 없는 특성으로 인해 이전 버전의 Swift와는 매우 다르게 사용된다.
예를 들어, 이런 종류의 코드는 특별한 것이 없는 것처럼 보일 수 있다:
func createUser() {
let newUser = User(name: "Anonymous")
var userCopy = newUser
print(userCopy.name)
}
createUser()
하지만 User
구조체를 복사할 수 없는 것으로 선언했는데, 어떻게 newUser
의 복사본을 가져올 수 있을까요?
정답은 '복사할 수 없다': newUser
를 userCopy
에 할당하면 원래의 newUser
값이 소비되어, 소유권이 이제 userCopy
에 속하기 때문에 더 이상 사용할 수 없게 된다는 뜻이다. print(userCopy.name)
을 print(newUser.name)
으로 변경하려고 하면 Swift에서 컴파일러 오류가 발생하는데, 이는 허용되지 않는다.
복사할 수 없는 유형을 함수 매개변수로 사용하는 방법에도 새로운 제한이 적용된다:
SE-0377에 따르면 함수는 값을 소비하여 함수가 완료된 후 호출 사이트에서 값을 무효화할 것인지, 아니면 값을 차용하여 코드의 다른 차용 부분과 동시에 모든 데이터를 읽을 수 있도록 할 것인지를 지정해야 한다.
따라서 사용자를 생성하는 함수를 하나 작성하고, 그 데이터에 대한 읽기 전용 액세스 권한을 얻기 위해 사용자를 차용하는 다른 함수를 작성할 수 있다:
func createAndGreetUser() {
let newUser = User(name: "Anonymous")
greet(newUser)
print("Goodbye, \(newUser.name)")
}
func greet(_ user: borrowing User) {
print("Hello, \(user.name)!")
}
createAndGreetUser()
반대로 greet()
함수가 consuming User
를 사용하도록 했다면 print("Goodbye, \(newUser.name)")
는 허용되지 않으며, Swift는 greet()
가 실행된 후 newUser
값을 유효하지 않은 것으로 간주할 것이다. 반대로, 메서드를 사용하면 객체의 수명이 종료되어야 하므로 객체의 속성을 자유롭게 변경할 수 있다.(?)
이러한 공유 동작은 복사 불가능한 구조체에 이전에는 클래스와 액터에만 국한되었던 강력한 기능을 부여한다.
복사 불가능한 인스턴스에 대한 최종 참조가 파괴될 때 자동으로 실행되는 이니셜라이저를 제공할 수 있다.
중요: 이것은 클래스의 이니셜라이저와 약간 다르게 동작하는데, 이는 초기 구현상의 결함일 수도 있고 고의적인 동작일 수도 있다.
먼저 클래스에 이니셜라이저를 사용하는 코드를 살펴보자:
class Movie {
var name: String
init(name: String) {
self.name = name
}
deinit {
print("\(name) is no longer available")
}
}
func watchMovie() {
let movie = Movie(name: "The Hunt for Red October")
print("Watching \(movie.name)")
}
watchMovie()
이것이 실행되면 "Watching The Hunt for Red October"와 "The Hunt for Red October is no longer available"가 출력된다.
하지만 타입의 정의를 'class Movie'에서 'struct Movie: ~Copyable'로 변경하면 두 개의 print()
문이 역순으로 실행되어 영화가 더 이상 제공되지 않는다고 표시된 다음 시청 중이라는 메시지가 표시된다.
복사 불가능한 타입 내부의 메서드는 기본적으로 차용이지만 복사 가능한 타입처럼 mutating
으로 표시할 수 있으며, 메서드가 실행된 후 값이 유효하지 않다는 의미로 consuming으로 표시할 수도 있다.
예를 들어, 비밀 요원들이 한 번만 재생할 수 있는 자폭 테이프를 통해 임무 지시를 받는 영화 및 TV 시리즈 '미션 임파서블'을 예로 들어보자. 이런 소모적인 방식에 딱 맞는다:
struct MissionImpossibleMessage: ~Copyable {
private var message: String
init(message: String) {
self.message = message
}
consuming func read() {
print(message)
}
}
이는 메시지 자체를 비공개로 표시하므로 인스턴스를 소비하는 consuming read()
메서드를 호출해야만 액세스할 수 있다.
변경하는 메서드와 달리 소비하는 메서드는 해당 유형의 상수 인스턴스에서 실행할 수 있다. 따라서 이와 같은 코드는 괜찮다:
func createMessage() {
let message = MissionImpossibleMessage(message: "You need to abseil down a skyscraper for some reason.")
message.read()
}
createMessage()
참고: message.read()
는 message
인스턴스를 소비하기 때문에 message.read()
를 두 번 호출하면 오류가 발생한다.
소비 메서드를 이니셜라이저와 함께 사용하면 정리 작업이 두 배로 늘어날 수 있으므로 조금 더 복잡해진다.
예를 들어, 게임에서 최고 점수를 추적하는 경우 가장 최근의 최고 점수를 영구 저장소에 쓰고 다른 사람이 점수를 더 이상 변경하지 못하도록 막는 소모적인 finalize()
메서드가 필요하지만, 객체가 파괴될 때 최신 점수를 디스크에 저장하는 이니셜라이저를 또한 사용할 수 있다.
이 문제를 방지하기 위해 Swift 5.9에서는 복사할 수 없는 유형의 메서드를 소비하는 데 사용할 수 있는 새로운 discard
연산자를 도입했다. 소비하는 메서드에 discard self
를 사용하면 이 객체에 대한 이니셜라이저의 실행이 중지된다.
따라서 HighScore
구조체를 다음과 같이 구현할 수 있다:
struct HighScore: ~Copyable {
var value = 0
consuming func finalize() {
print("Saving score to disk…")
discard self
}
deinit {
print("Deinit is saving score to disk…")
}
}
func createHighScore() {
var highScore = HighScore()
highScore.value = 20
highScore.finalize()
}
createHighScore()
팁: 해당 코드가 실행되면 value
프로퍼티를 변경하여 구조체를 효과적으로 파괴하고 다시 생성할 때 한 번, createHighScore()
메서드가 완료될 때 한 번, 이니셜라이저 메시지가 두 번 인쇄되는 것을 볼 수 있다.
이 새로운 기능으로 작업할 때 주의해야 할 몇 가지 추가 복잡성이 있다:
- Class와 Actor는 Noncopyable할 수 없다.
- Noncopyable 유형은 현재 제네릭을 지원하지 않으므로
Optional Noncopyable Object와 Noncopyable Object의 배열도 당분간 사용할 수 없다. - Noncopyable 유형을 다른 구조체나 열거형 내부의 프로퍼티로 사용하는 경우 해당 상위 구조체나 열거형도 복사 불가능해야 하다.
- 기존 유형에서 'Copyable'을 추가하거나 제거하면 사용 방식이 크게 달라지므로 매우 신중해야 하다. 라이브러리에서 코드를 배포하는 경우 ABI(?)가 손상될 수 있다.
이것은 Swift에서 정말 광범위한 변화이며, 어떻게 사용될지 정말 궁금하다. 답이 "Swift Data"가 아니라면 실망할 것 같다!
모든 문장을 100% 이해했다기 보다는
Noncopyable 을 이용하면 값이 1번 소모되는 형태로 전달할 수 있을 것 같다는 느낌 정도로 보고있다.
자세한건 직접 써봐야 깊게 이해할 것 같다.
'Apple > Swift' 카테고리의 다른 글
[Swift 5.9] Convenience Async[Throwing]Stream.makeStream methods (1) | 2024.03.07 |
---|---|
[Swift 5.9] Consume operator to en the lifetime of a variable binding (4) | 2024.03.06 |
[Swift 5.9] Macro (0) | 2024.01.11 |
[Swift 5.9] Value and Type Parameter Packs (0) | 2024.01.09 |
[Swift 5.9] if and switch expressions (1) | 2024.01.08 |
- Total
- Today
- Yesterday
- 개발신입
- analysis_options
- flutter 3.7
- SWIFT
- sliver
- jsonkey
- 티스토리챌린지
- tojson()
- 오블완
- lints
- swiftmigration
- 낙관적 업데이트 패턴
- 플러터
- 플러터 업데이트
- 플러터업데이트
- llm 설치
- ios
- flutter3.7
- 오블완챌린지
- flutter updates
- slivers
- flutter
- 다트문법
- 렌더링최적화
- jsonserializable
- flutter_lints
- 플러터ios애니메이션
- 플러터네이티브
- sliverlist
- fromjson()
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |