스위프트 표준 라이브러리는 Array, Dictionary, Set 등 중요한 컨테이너 타입을 제공합니다.
더 나은 성능을 발휘하려면 컨테이너 타입을 어떻게 사용해야 하는지 알아봅니다.
Array 안에 값타입 사용하기Objective-C를 사용했던 프로그래머라면 NSArray에 익숙할겁니다. Objective-C의 NSArray는 값 타입을 담을 수 없죠. 스위프트는 Array 타입을 사용할 때 Objective-C와의 호환을 위해 NSArray와 브릿징을 준비해둡니다. 그렇지만 값 타입을 Array의 요소로 사용한다면 NSArray와 브릿징이 원천적으로 불가하죠. 그래서 Array에 값 타입을 사용한다면 컴파일러는 브릿징 가능성을 배제하고 최적화합니다.
또, 값 타입은 참조 카운팅(reference counting)이 발생하지 않기 때문에 런타임 성능에 더 유리합니다.
하지만 값 타입이 항상 만능인 것은 아닙니다.
큰 규모의 값 타입은 값을 복사하는 경우에 큰 자원을 소비하기 때문에, 그럴때는 참조 타입이 더 유리할 수 있다는 점을 기억해두세요.
NSArray 브릿징이 필요하지 않은 경우에는 ContiguousArray를 활용하기참조 타입을 요소로 갖는 배열이 필요한 경우가 있죠. 만약 참조 타입을 요소로 갖지만, NSArray로의 브릿징이 필요하지 않은 경우에는 ContiguousArray 활용이 유리합니다.
class SampleClass { }
let samples: ContiguousArray = [SampleClass(), SampleClass(), SampleClass()]
스위프트의 모든 컨테이너는 COW(copy-on-write)을 활용하는 값 타입입니다.
COW를 통해 꼭 필요한 경우가 아니라면 깊은 복사(deep copy)를 하지 않을수 있습니다. 실질적인 깊은 복사는 컨테이너가 다른곳으로 복사된 후 변경이 될 때 일어납니다.
아래 코드에서 second에 first를 할당할 때는 실질적인 깊은 복사가 이뤄지지 않습니다. 변경이 이뤄지지 않았기 때문이죠. 그러나 second.append(4)처럼 다른 요소를 덧붙이거나 third = append_one(1)처럼 인스턴스를 재할당 하는 경우에는 깊은 복사, 즉, COW가 발생합니다.
var first: [Int] = [1, 2, 3]
var second: [Int] = first // 깊은 복사 일어나지 않음
second.append(4) // 변경이 일어날 때 실질적인 깊은 복사가 일어남
func append_one(_ input: [Int]) -> [Int] {
var output: [Int] = input
output.append(1)
return output
}
var third = [1, 2, 3]
third = append_one(1) // 깊은 복사 발생
append_one 함수를 활용했을 때는 배열을 전체적으로 다시 할당하기 때문에 깊은 복사가 일어나는데, 깊은 복사가 일어나도록 하는 대신에 배열 내부의 요소를 inout 매개변수를 활용하여 바꿔준다면 재할당도 일어나지 않고, 깊은 복사도 방지할 수 있습니다.
func append_one_in_place(input: inout [Int]) {
input.append(1)
}
var fourth: [Int] = [1, 2, 3]
append_one_in_place(&fourth) // 깊은 복사 발생하지 않음