iOS 개발자가 많이 하는 실수 - Array append 반복 사용 vs reserveCapacity / Array(repeating:count:) 성능 문제
Dev Study/iOS 2025. 12. 4. 19:47반응형
iOS 개발자가 많이 하는 실수 - Array append 반복 사용 vs reserveCapacity / Array(repeating:count:) 성능 문제
1. 흔히 쓰는 패턴
var list: [Int] = []
for i in 0..<10_000 {
list.append(i)
}
이 코드는 문법적으로 전혀 문제가 없고, 작은 데이터에서는 성능 문제도 잘 느껴지지 않습니다.
그래서 많은 초보자는 “배열은 그냥 append 하는 거지” 정도로 이해하고 넘어갑니다.
하지만 요소 개수가 많아질수록, 그리고 이 패턴이 여러 곳에 등장할수록
성능과 메모리 효율 측면에서 손해를 볼 수 있습니다.
2. Swift Array 내부 동작 개념
Swift의 Array는 내부적으로 동적 배열(Dynamic Array) 구조입니다.
- 처음에는 어느 정도 크기의 버퍼를 할당
- 공간이 부족해지면 더 큰 버퍼를 다시 할당하고, 기존 데이터를 복사한 후 사용
- 이 과정에서:
- 새 메모리 할당
- 기존 원소 복사
- 이전 메모리 해제
가 일어나기 때문에 비용이 큼
append를 반복하면:
- capacity가 꽉 찰 때마다 위 과정을 반복
- 특히 수만~수십만 개 단위로 쌓이면 누적 비용이 무시 못 할 수준이 될 수 있음
3. 성능 문제 예시
var list: [Int] = []
for i in 0..<100_000 {
list.append(i) // 필요 시마다 capacity 늘림
}
- append 자체가 느린 게 아니라,
- 중간중간 발생하는 “capacity 확장 + 복사”가 누적되어 성능을 떨어뜨립니다.
Swift는 내부적으로 성장 전략(예: 2배씩 증가 등)을 사용해 재할당 횟수를 줄이지만,
“처음부터 몇 개를 넣을지 알고 있는 경우”라면 미리 capacity를 확보하는 쪽이 훨씬 효율적입니다.
4. 해결 방법 1: reserveCapacity(_:) 사용
4-1. 기본 패턴
var list: [Int] = []
list.reserveCapacity(10_000) // 미리 10,000개 용량 확보
for i in 0..<10_000 {
list.append(i)
}
이렇게 하면:
- 재할당 횟수 감소
- 전체 복사 비용 감소
- 반복적으로 사용될 때 성능 안정성 향상
4-2. 언제 쓰면 좋은가?
- 반복문으로 정확히 또는 대략적인 개수를 알 때
- 파서/필터/정렬 등에서 배열을 많이 만드는 코드
- 대규모 데이터 처리 (로그, 통계, 검색 결과 등)
5. 해결 방법 2: Array(repeating:count:) 사용
5-1. 동일한 값으로 초기화할 때
let zeros = Array(repeating: 0, count: 10_000)
- 10,000개의 0으로 채워진 배열 생성
- 이 패턴은 다음과 같이 append 반복으로 만드는 것보다 훨씬 효율적입니다.
var zeros: [Int] = []
zeros.reserveCapacity(10_000)
for _ in 0..<10_000 {
zeros.append(0)
}
위와 같은 코드를 직접 짤 필요 없이
Array(repeating:count:)이 최적화된 형태로 처리해 줍니다.
6. 언제 append 반복만으로도 괜찮은가?
다음과 같은 경우에는 굳이 reserveCapacity까지 신경 쓰지 않아도 됩니다.
- 원소 개수가 매우 작은 컬렉션 (수십 개 수준)
- 성능이 전혀 중요하지 않은, 일회성 유틸 코드
- 이 부분이 전체 성능에서 병목이 되지 않는 것이 확실한 경우
그러나:
- 루프 안에서 수천·수만 번 호출
- UI 스크롤이나 실시간 처리(검색, 필터링)와 결합되었을 때
에는 capacity 전략을 의식적으로 설계하는 것이 좋습니다.
7. 실무 스타일 예시
나쁜 예 (무지성 append)
func buildIndices(from items: [Item]) -> [Int] {
var indices: [Int] = []
for (idx, item) in items.enumerated() {
if item.isValid {
indices.append(idx)
}
}
return indices
}
개선 예 (대략적인 개수라도 고려)
func buildIndices(from items: [Item]) -> [Int] {
var indices: [Int] = []
indices.reserveCapacity(items.count)
for (idx, item) in items.enumerated() {
if item.isValid {
indices.append(idx)
}
}
return indices
}
- 실제로는 isValid 조건 때문에 items.count보다 적게 들어갈 수 있지만,
- 그럼에도 미리 capacity를 넉넉히 잡는 편이 재할당을 줄이는 데 도움이 됩니다.
8. 정리
- append를 반복하면 동적 배열의 특성상 capacity 확장 + 복사 비용이 누적된다.
- 많은 원소를 다루는 코드에서는:
- reserveCapacity(_:)로 미리 용량 확보
- 동일 값 배열은 Array(repeating:count:)로 초기화
- 작은 데이터나 비성능-critical 코드에서는 단순 append도 괜찮지만,
“반복적으로 많이 쓰이는 코드”라면 capacity 설계까지 보는 것이 좋다.
반응형
'Dev Study > iOS' 카테고리의 다른 글
| iOS 개발자가 많이 하는 실수 - Codable decode 실패 시 에러 원인 확인 없이 try? 사용 (0) | 2025.12.04 |
|---|---|
| iOS 개발자가 많이 하는 실수 - switch-case에서 default를 사용하고 enum 케이스 추가 시 실수 (0) | 2025.12.04 |
| iOS 개발자가 많이 하는 실수 - Dictionary에서 key 존재 여부 확인 없이 강제 언래핑(!) (0) | 2025.12.04 |
| iOS 개발자가 많이 하는 실수 - struct vs class 차이를 오해하여 발생하는 문제 (Value Type / Reference Type 혼동) (1) | 2025.12.04 |
| iOS 개발자가 많이 하는 실수 - 문자열 비교 시 lowercased() 반복 사용 문제 (0) | 2025.12.04 |
| iOS 개발자가 많이 하는 실수 - guard를 잘못 사용(Early Exit Misuse) (0) | 2025.12.04 |
| iOS 개발자가 많이 하는 실수 - Optional Binding 중첩(if let 지옥) (0) | 2025.12.04 |
| iOS 개발자가 많이 하는 실수 - 옵셔널을 강제 언래핑(!)하기 (0) | 2025.12.04 |

