2025. 6. 3. 22:52ㆍSwift, iOS Foundation
요즘은 메모리 관리와 관련된 내용을 공부 중입니다.
Swift에서 메모리와 관련된 가장 핵심 개념은 ARC (Automatic Reference Counting)라고 볼 수 있지만,
오늘 글에서는 CS 기초 개념쪽에 더 가까운 내용을 다루게 갈 것이구요. 앞으로도 메모리 관리나 디버깅과 관련된 다소 생소(?)할 수 있는 글들이 많이 올라올 것 같습니다!
오늘은 그 메모리 개념의 가장 기초적인 가벼운 개념 COW (Copy-On-Write)부터 만나러 가보시죠!
예전 Swift 메모리와 관련된 글은 아래 링크를 들어가시면 확인할 수 있습니다.
- [Swift] ARC (Automatic Reference Counting) 완전 정복하기
- [Swift] [weak self] 이젠 제대로 알고 사용하자! (feat. ARC 2탄)
얕은 복사 (Shallow Copy)는 뭐고, 깊은 복사 (Deep Copy)는 뭔데?
이 둘은 메모리에서 Swift의 데이터를 어떻게 복사하는가, 그리고 복사된 데이터가 원본 데이터와 얼마나 독립적인가에 대한 이야기입니다.
- 얕은 복사 (Shallow Copy) : 객체의 참조만 복사, 내부 데이터는 사실 공유되고 있음 -> 클래스가 해당되는 참조 타입 (Reference Type)의 복사 전략
- 깊은 복사 (Deep Copy) : 객체를 아예 새로 만들어서, 내부 데이터의 내용물까지 모두 복사하는 것 -> 구조체와 열거형이 해당되는 값 타입 (Value Type)의 복사 전략
하지만, 사실 참조 타입에서도 깊은 복사 (Deep Copy)를 적용할 수 있습니다.
직접 깊은 복사를 수행하는 커스텀 메서드 (deepCopy() 혹은 clone())를 구현하거나
Objective-C 스타일의 NSCopying 프로토콜을 클래스에 채택하여, copy() 메서드를 구현하는 방식으로 만들 수 있죠.
아래 예시 코드를 참고하며 이해해 봅시다!
// 깊은 복사를 수행하는 커스텀 메서드로 깊은 복사 구현하기
class Person: NSCopying {
var name: String
init(name: String) {
self.name = name
}
func deepCopy() -> User {
return Person(name: self.name)
}
}
let original = Person(name: "Mini")
let cloned = original.deepCopy()
cloned.name = "Mini2"
print(original.name) // ✅ "Mini"
print(cloned.name) // ✅ "Mini2"
// Objective-C 스타일의 NSCopying 프로토콜로 깊은 복사 구현하기
class Person: NSCopying {
var name: String
init(name: String) {
self.name = name
}
func copy(with zone: NSZone? = nil) -> Any {
return Person(name: self.name)
}
}
let original = Person(name: "Mini")
let cloned = original.copy() as! Person
cloned.name = "Mini2"
print(original.name) // ✅ "Mini"
print(cloned.name) // ✅ "Mini2"
COW (Copy-On-Write)란 무엇일까?
자 이제 원래 하고 싶었던 얘기로 들어갈 차례입니다. COW가 무엇인지 설명 들어가봅니다.
COW (Copy-On-Write)는 직역해보면 "작성 시 복사"입니다.
조금 더 자세하게 들어가면 값이 여러 곳에서 공유될 때, 수정이 발생할 때만 (= 작성 시) 복사를 수행하는 전략이라고 해석할 수 있죠.
- 처음에는 얕은 복사 (Shallow Copy)가 수행 -> 이후 수정 (Write)하려 할 때, 원본이 다른 곳에서 공유 중이라면 그때 깊은 복사 (Deep Copy)가 발생한다는 원리
- Swift 표준 라이브러리의 Array, Dictionary, Set과 같은 컬렉션 타입 (Collection Type)은 기본적으로 COW (Copy-On-Write)를 적용하고 있습니다.
- 뿐만 아니라, Swift 4 이후의 String과 Data 타입도 모두 내부적으로 COW가 적용되어 복사가 진행됩니다.
아래 예시 코드를 보면서 COW (Copy-On-Write)가 무엇인지 더 직접적으로 느껴보죠.
a와 b는 Collection Type인 Array이므로, 기본적으로 COW (Copy-On-Write)가 적용되어 있습니다.
즉 b = a 코드에서는 얕은 복사 (Shallow Copy)가 이루어지는 것에서 -> 실제 b에 값이 변경되는 순간 (b.append())에 깊은 복사 (Deep Copy)로 바뀌는 상황을 확인할 수 있죠.
var a = [1, 2, 3]
var b = a // 참조만 복사됨 (깊은 복사 같지만, 사실 이 시점에서는 "얕은 복사")
print(a) // [1, 2, 3]
print(b) // [1, 2, 3]
b.append(4) // 이 시점 (Write가 발생한 시점)에 비로소 "깊은 복사"가 이루어진 것!
print(a) // [1, 2, 3]
print(b) // [1, 2, 3, 4]
Why COW? | 왜 Copy-On-Write가 필요한거지?
너무나 당연하게도 성능이 좋아지기 때문입니다.
"오잉? 어떤 성능이 좋아진다는 것이지...?"조금 더 자세하게 들어가자면!
최대한 깊은 복사가 필요한 경우 (= Write가 이루어져, 진짜 분리가 필요해졌을 때)에만 복사가 이루어지니, 메모리 낭비를 최소화할 수 있을 겁니다.
또힌, 수정 시에 비로소 깊은 복사가 이루어지니, 원본 데이터를 최대한 보호한 상황에서 복사가 이루어질 수 있죠.
이런 Swift의 메모리 안정성 상황을 문장으로 "값 타입은 불변처럼 다뤄지면서도, 복사 성능은 참조 타입처럼 최적화된다."라고 정의할 수 있습니다.
즉 정리하자면, 얕은 복사의 빠른 속도 + 깊은 복사의 안정성을 모두 결합하는 최적의 방법이 COW (Copy-On-Write)인 것입니다!
'Swift, iOS Foundation' 카테고리의 다른 글
[WWDC25] 2025 Platforms State of the Union 빠르게 톺아보기 (2) | 2025.06.12 |
---|---|
[GCD] 메인 스레드에서 DispatchQueue.main.sync를 사용하면 안되는 이유 (1) | 2025.06.06 |
[Swift] Swift Equatable 완전 정복하기 (0) | 2025.04.28 |
[iOS] Swift의 UI 작업은 반드시 Main Thread에서 동작해야한다? (0) | 2025.04.07 |
[Swift] some, any 제대로 구분해서 사용하기 (Opaque and Boxed Protocol Types in Swift) (0) | 2025.02.26 |