컨테이너 타입 효율적으로 활용하기

스위프트 표준 라이브러리는 Array, Dictionary, Set 등 중요한 컨테이너 타입을 제공합니다.
더 나은 성능을 발휘하려면 컨테이너 타입을 어떻게 사용해야 하는지 알아봅니다.

1. Array 안에 값타입 사용하기

Objective-C를 사용했던 프로그래머라면 NSArray에 익숙할겁니다. Objective-C의 NSArray는 값 타입을 담을 수 없죠. 스위프트는 Array 타입을 사용할 때 Objective-C와의 호환을 위해 NSArray와 브릿징을 준비해둡니다. 그렇지만 값 타입을 Array의 요소로 사용한다면 NSArray와 브릿징이 원천적으로 불가하죠. 그래서 Array에 값 타입을 사용한다면 컴파일러는 브릿징 가능성을 배제하고 최적화합니다.

또, 값 타입은 참조 카운팅(reference counting)이 발생하지 않기 때문에 런타임 성능에 더 유리합니다.

하지만 값 타입이 항상 만능인 것은 아닙니다.
큰 규모의 값 타입은 값을 복사하는 경우에 큰 자원을 소비하기 때문에, 그럴때는 참조 타입이 더 유리할 수 있다는 점을 기억해두세요.

2. 참조타입을 다룰 때 NSArray 브릿징이 필요하지 않은 경우에는 ContiguousArray를 활용하기

참조 타입을 요소로 갖는 배열이 필요한 경우가 있죠. 만약 참조 타입을 요소로 갖지만, NSArray로의 브릿징이 필요하지 않은 경우에는 ContiguousArray 활용이 유리합니다.

class SampleClass {  }
let samples: ContiguousArray = [SampleClass(), SampleClass(), SampleClass()]

3. 컨테이너의 전체 인스턴스 재할당 대신에 내부 값을 대치하는 방식 사용하기

스위프트의 모든 컨테이너는 COW(copy-on-write)을 활용하는 값 타입입니다.
COW를 통해 꼭 필요한 경우가 아니라면 깊은 복사(deep copy)를 하지 않을수 있습니다. 실질적인 깊은 복사는 컨테이너가 다른곳으로 복사된 후 변경이 될 때 일어납니다.

아래 코드에서 secondfirst를 할당할 때는 실질적인 깊은 복사가 이뤄지지 않습니다. 변경이 이뤄지지 않았기 때문이죠. 그러나 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)  // 깊은 복사 발생하지 않음