
struct SecretMessage: ~Copyable {
private(set) var content: String
private(set) var isEncrypted: Bool = false
init(content: String) {
self.content = content
}
mutating func encrypt() {
self.content = "[Encrypted] \(self.content)"
self.isEncrypted = true
}
}
protocol MessageProcessable {
func inspect(_ message: borrowing SecretMessage) -> Bool
func encryptMessage(_ message: inout SecretMessage)
func archive(_ message: consuming SecretMessage)
}
final class SecureMessageHandler: MessageProcessable {
func inspect(_ message: borrowing SecretMessage) -> Bool {
print("🔍 Inspecting message: \(message.content)")
return !message.content.isEmpty
}
func encryptMessage(_ message: inout SecretMessage) {
print("🔒 Encrypting message...")
message.encrypt()
}
func archive(_ message: consuming SecretMessage) {
print("d Archiving message: \(message.content)")
}
}
해당 값 타입 객체의 소유권을 함수에게 넘긴다는 의미
소유권을 함수에게 완전히 넘겨 기존 호출자는 해당 값을 사용할 수 없게 함.
protocol MessageProcessable {
// Consume: 메시지를 전송하거나 파기함 (소유권 종료)
func archive(_ message: consuming SecretMessage)
}
final class SecureMessageHandler: MessageProcessable {
// consuming: 소유권을 가져와서 생명주기를 끝내거나 소유권을 가짐.
func archive(_ message: consuming SecretMessage) {
print("d Archiving message: \(message.content)")
// 함수가 종료되면 message는 메모리에서 해제
// 여기서 해당 클래스 내 변수에 저장하면 해제 X 및 소유권 보유
}
}
func performTest(processor: MessageProcessable) {
var message = SecretMessage(content: "Top Secret Codes")
// 소유권을 완전히 processor에게 넘김.
processor.archive(message)
// 소유권이 넘어갔으므로 에러 발생
// 이제 message 변수에는 접근 불가
// processor.inspect(message) // Error: 'message' used after consume
}
consuming 키워드로 정의된 매개변수에 객체를 넣으면 해당 함수에게 객체의 소유권이 넘어감.
기존 해당 객체의 소유권을 지닌 변수는 재접근이 불가.
struct LargeImage {
var data: [UInt8]
var metadata: String
init() {
self.data = Array(repeating: 0, count: 10 * 1024 * 1024)
self.metadata = "High Res Photo"
}
}
class ImageService: ImageProcessable {
// 기존 소유자가 더 이상 이 값을 안 쓸 때, 소유권을 가지게 됨.
func upload(_ image: consuming LargeImage) {
print("☁️ Uploading data...")
}
}
func performanceTest() {
let service = ImageService()
let myPhoto = LargeImage()
// Consuming
// Copyable이므로 복사 발생
service.upload(myPhoto)
// 여전히 사용 가능
print("📸 Still have photo: \(myPhoto.metadata)")
// 마지막 사용 시점에 자동으로 최적화
service.upload(myPhoto) // 여기서 myPhoto가 마지막으로 쓰인다면 최적화 수행 (소유권 넘기기)
}
기존 소유자가 consuming으로 소유권을 넘겼으나, 후에 재사용되면 복사된 형태로 값을 넘김.
마지막으로 재사용되면 컴파일러가 해당 값의 소유권을 마지막으로 consuming한 함수에게 넘김.
즉, 여러 번 consuming 되면 마지막이 원본을 갖고, 앞의 함수들은 복사된 값을 가짐.
소유권을 넘기지 않고 읽기 권한만 넘기기
호출된 함수에게 읽기 권한만을 주어 읽기만 가능
protocol MessageProcessable {
// Borrow: 내용을 확인만 함 (소유권 유지)
func inspect(_ message: borrowing SecretMessage) -> Bool
}
final class SecureMessageHandler: MessageProcessable {
// borrowing: 원본에 영향을 주지 않고 읽기만 수행
func inspect(_ message: borrowing SecretMessage) -> Bool {
print("🔍 Inspecting message: \(message.content)")
return !message.content.isEmpty
}
}
func performTest(processor: MessageProcessable) {
var message = SecretMessage(content: "Top Secret Codes")
// 소유권은 여전히 'performTest' 함수에 있음
let isValid = processor.inspect(message)
assert(isValid, "Message should be valid")
struct LargeImage {
var data: [UInt8]
var metadata: String
init() {
self.data = Array(repeating: 0, count: 10 * 1024 * 1024)
self.metadata = "High Res Photo"
}
}
class ImageService: ImageProcessable {
// borrowing을 쓰지 않은 함수에서는 값 복사 발생 가능성
// 값을 복사하지 않고 포인터로 참조만 하여 오버헤드 0
func analyze(_ image: borrowing LargeImage) {
// image.metadata = "New" // Error: borrowing은 수정 불가
print("🔍 Analyzing \(image.data.count) bytes without copying...")
}
}
func performanceTest() {
let service = ImageService()
let myPhoto = LargeImage()
// 복사 없이 메모리 주소만 참조해서 읽음
service.analyze(myPhoto)
}
기존 방식에서 값 타입을 넘기면 해당 값이 복사되어 함수로 넘겨가던 방식
borrowing 키워드로 값을 복사하지 않고 포인터로 참조만하여 읽기 가능
소유권을 대여하여 해당 값을 수정
소유권을 잠시 가져 수정 권한을 얻는 것
protocol MessageProcessable {
// Mutate (inout): 내용을 변경함 (소유권 대여 후 반환)
func encryptMessage(_ message: inout SecretMessage)
}
final class SecureMessageHandler: MessageProcessable {
// inout: 값을 변경할 수 있음
func encryptMessage(_ message: inout SecretMessage) {
print("🔒 Encrypting message...")
message.encrypt()
}
}
func performTest(processor: MessageProcessable) {
var message = SecretMessage(content: "Top Secret Codes")
// 소유권을 잠시 processor에게 빌려주고, 수정된 상태로 돌려받음
processor.encryptMessage(&message)
우리가 사용하던 inout 방식과 동일
호출한 함수가 끝날 때까지 해당 값의 배타적 접근 보장 필요
해당 값의 원본 그 자체를 가져와 수정하고 돌려주는 방식
~Copyable에서는 원본 자체를 가져와 수정했다면 Copyable은 복사본을 수정한 뒤에 복사본을 원본에 덮어쓰는 방식https://github.com/swiftlang/swift-evolution/blob/main/proposals/0427-noncopyable-generics.md
https://developer.apple.com/videos/play/wwdc2024/10170/
https://docs.swift.org/swift-book/documentation/the-swift-programming-language/memorysafety/