[TIL / 25.03.20] 최적화(OptimizationTips) 2

2025. 3. 20. 19:33·iOS/Swift

1. Swift 컨테이너 타입을 효율적으로 사용하는 방법

Array에서 값 타입 사용하기

Swift에서 타입은 두 가지 카테고리로 나눌 수 있습니다. 값 타입과 참조 타입.

값 타입은 struct, enum, tuple이 있고, 참조 타입은 class가 있는데, 값 타입의 경우 NSArray 내부에 포함될 수 없습니다.

여기서 NSArray가 무엇인가? 

NSArray는 Objective-C에서 사용되는 불변(Immutable) 배열 클래스라고 합니다. 스위프트에선 옵젝씨와의 브릿징을 통해 사용할 수 있다고 합니다. 오직 객체(참조 타입)만 저장 가능합니다.

 

Swift의 Array는 내부적으로 때로는 Objective-C의 NSArray와 호환되어야 한다고 합니다. 다만, 값 타입은 NSArray에 넣을 수 없는데, 이렇기에 컴파일러는 NSArray로 바뀔 일이 없구나라고 판단하고 그런 변환 가능성을 처리하는 코드를 모두 제거해버린다고 합니다.

이런 불필요한 코드가 제거되면 실행 속도가 빨라집니다.

 

그리고 클래스(참조 타입)는 항상 참조 카운팅이 필요합니다. 다만, struct 같은 값 타입은 그 안에 클래스가 들어있지 않으면 참조 카운팅이 필요 없다고 하는데 이 참조 카운팅은 메모리 관리를 위한 추가 작업이라 이게 없으면 더 빠르게 작동합니다.

 

결국 Array에서 가능하면 값 타입을 사용하세요인데, 이는 NSArray 브리징 오버헤드를 피하기 위함이라고 보면 될 것 같습니다.

 

// 이렇게 struct를 쓰면 성능이 좋아요
struct PhonebookEntry {
  var name: String  // String은 비록 내부적으로 참조 타입을 갖지만 최적화되어 있음
  var number: [Int] // Int는 값 타입
}

var contacts: [PhonebookEntry]  // 이 배열은 더 효율적으로 동작함

 

다만 주의해야 할 점은, 너무 큰 구조체를 만들면 복사할 때 비용이 많이 들기에 이럴땐 클래스가 나을 수 있으니 잘 생각해보고 선택해야 합니다.

 

참조 타입 배열의 효율적인 사용법

ContiguousArray 사용하기

클래스(참조 타입) 객체들의 배열이 필요하고, 이 배열이 NSArray로 변환될 필요가 없다면 일반 Array 대신 ContiguousArray를 사용하는게 좋답니다. ContiguousArray는 NSArray와의 브릿징(변환) 코드가 없어서 더 효율적입니다.

class C { ... }
var a: ContiguousArray<C> = [C(...), C(...), ..., C(...)]

 

즉, 만약 꼭 참조 타입(class)이 필요하다면, 일반 Array 대신 ContiguousArray를 사용하면 됩니다.

NSArray 호환성이 필요한 경우로는 Objective-C 코드와 상호작용하는 경우나 Foundation 프레임워크의 특정 API 사용 시 그리고 UIKit/AppKit 등 Apple의 오래된 프레임워크를 사용할 때 필요하며 필요하지 않은 경우는 순수 Swift 코드만 사용하는 경우 혹은 성능이 중요한 내부 로직, 그리고 Swift만의 값 타입 시스템을 활용할때 필요하지 않다고 합니다.

 

결국 값 타입의 배열은 일반 Array, 참조 타입의 배열인데 NSArray 호환이 필요하면 Array, 호환이 불필요하면 ContigousArray를 쓰면된다. (배열 내에 타입 혼용은 허용되지 않기에 참조인지 값 타입인지 판단 후 호환 필요 여부를 따지고 선택해보면 좋을듯)

 

이 부분은 한번 따로 정리 좀 해야할 필요가 있을듯.

 

COW(Copy-On-Write) 매커니즘 이해하기

COW란?

swift의 모든 표준 컨테이너(Array, Dictionary 등)는 값 타입이지만, 실제로는 COW(Copy-On-Write) 방식을 사용합니다.

COW는 실제로 값이 변경될 때만 복사를 수행하는 최적화 기법입니다.

var c: [Int] = [ ... ]
var d = c        // 여기서는 복사가 일어나지 않음 (내부 데이터를 공유)
d.append(2)      // 여기서 실제 복사가 일어남 (d가 변경되기 때문)

 

이 예시에서 d = c를 할 때는 실제 데이터 복사 없이 같은 메모리를 참조합니다. 그 후 d.append(2)처럼 d를 변경할 때만 복사가 발생하고, 그 후에 2가 추가됩니다.

 

예상치 못한 복사를 피하는 방법

함수를 통한 객체 재할당 방식은 불필요한 복사를 유발할 수 있는데

func append_one(_ a: [Int]) -> [Int] {
  var a = a      // 여기서 불필요한 복사가 발생할 수 있음
  a.append(1)
  return a
}

var a = [1, 2, 3]
a = append_one(a) // 새로운 배열을 반환받아 할당

 

Swift에서 모든 파라미터는 함수 호출 시 retain(유지)되고, 함수 종료 시 release(해제)됩니다. 이로 인해 불필요한 복사가 발생할 수 있습니다.

이를 해결하기 위해 inout 파라미터를 사용합니다.

func append_one_in_place(a: inout [Int]) {
  a.append(1)    // 원본을 직접 수정
}

var a = [1, 2, 3]
append_one_in_place(&a) // 원본을 직접 전달

 

inout 파라미터를 사용하면 함수에 원본 객체의 참조를 직접 전달합니다.(& 기호를 통해)

그리고 함수 내에서 원본을 직접 수정할 수 있고 새로운 객체를 생성하고 반환하는 오버헤드가 없어집니다.

'iOS > Swift' 카테고리의 다른 글

HarryPotterBooks 과제 1레벨 기록  (0) 2025.03.25
[TIL / 25.03.24] UIKit No Storyboard 초기 세팅  (0) 2025.03.24
[TIL / 25.03.19] playground, command line tool에서의 비동기 함수 (feat. escaping closure)  (2) 2025.03.19
[TIL / 25.03.18] 최적화(OptimizationTips) 1  (5) 2025.03.18
[TIL / 25.03.17] Array.compactMap (feat. 문법 심화 과제)  (0) 2025.03.17
'iOS/Swift' 카테고리의 다른 글
  • HarryPotterBooks 과제 1레벨 기록
  • [TIL / 25.03.24] UIKit No Storyboard 초기 세팅
  • [TIL / 25.03.19] playground, command line tool에서의 비동기 함수 (feat. escaping closure)
  • [TIL / 25.03.18] 최적화(OptimizationTips) 1
subkyu-ios
subkyu-ios
subkyu-ios 님의 블로그 입니다.
  • subkyu-ios
    subkyu-ios 님의 블로그
    subkyu-ios
  • 전체
    오늘
    어제
    • 분류 전체보기 (52)
      • iOS (35)
        • Swift (35)
      • 내일배움캠프 (7)
      • Git, Github (3)
      • Algorithm (6)
      • 회고 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    트러블슈팅
    의존성 주입
    TableView
    algorithm
    프로그래머스
    UIKit
    내일배움캠프
    KPT
    사전캠프
    tabman
    stackview
    RxSwift
    본캠프
    ios
    Wil
    github
    Swift
    회고
    알고리즘
    til
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
subkyu-ios
[TIL / 25.03.20] 최적화(OptimizationTips) 2
상단으로

티스토리툴바