일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
Tags
- rxswift
- 후기
- 코코아 인터널스
- Algorithm
- 부트캠프
- 알고리즘
- 단위 테스트
- boostcamp
- Swift
- World
- WWDC
- ios #swift #uialertcontroller #메서드 스위즐링
- Hello
- 부스트캠프
- Cocoa Internals
- 개발
- IOS
- 커스텀 뷰
- development
- Opensource
- Tistory
- 네이버 부캠
- notion
- Design Pattern
- SwiftUI
- OS
- 디자인패턴
Archives
- Today
- Total
꿈돌이랜드
TCA 아키텍처에서 IdentifiedArray를 사용해야 하는 이유 본문
반응형
Modern SwiftUI: Identified arrays
이 포스팅은 위 링크를 기반으로 작성했습니다.
IdentifiedArray가 필요한 이유
보통 SwiftUI에서 리스트 뷰를 구현할 때 ForEach 구문으로 리스트 데이터를 순차적으로 표시하는 케이스가 많습니다.
그런 경우 보통 다음과 같이 구현하게 됩니다.
struct StandupsList: View { @State var standups: [Standup] = […] var body: some View { List { ForEach(standups) { standup in StandupRow(standup: standup) } } } }
그런데 보통 뷰 레이어에서 다시 데이터를 조작해서 상태를 반영시켜주어야 하는 경우가 많은데, 그런 경우 다음과 같은 경우가 필연적으로 발생하게 됩니다.
func deleteStandup(id: Standup.ID) { guard let index = standups.firstIndex(where: { $0.id == id }) else { return } standups.remove(at: index) }
TCA의 경우, Reducer 쪽에 다음처럼 같이 작성될 수 있습니다.
struct StandupsList: Reducer { struct State: Equatable { var standups: [Standup] = [] } enum Action { case deleteStandup(id: Standup.ID) } func reduce(into state: inout State, action: Action) -> Effect<Action> { switch action { case let .deleteStandup(id): if let index = state.standups.firstIndex(where: { $0.id == id }) { state.standups.remove(at: index) } return .none } } }
문제는 firstIndex에 있습니다, firstIndex는 해당하는 요소를 찾기 위해 배열을 처음부터 스캔해야 하기 때문에(시간복잡도 O(n)) 잠재적인 성능 문제가 발생하는 지점이 되게 됩니다.
문서에는 다음과 같은 예시도 들어서, api 클라이언트를 통해 배열을 수정하게 되면 크래시가 나는 경우도 보여줍니다.
func deleteStandup(id: Standup.ID) async throws { guard let index = standups.firstIndex(where: { $0.id == id }) else { return } try await apiClient.delete(id: id) standups.remove(at: index) }
IdentifiedArray의 특징과 사용법
IdentifiedArray는 기존의 원시 배열을 대체하는 컬렉션 타입입니다.
ID로 요소를 안전하고 효율적으로 읽고 수정할 수 있는 메서드를 제공합니다.
IdentifiedArray에 들어갈 요소 타입의 id는 마치 identifiable 프로토콜 처럼 hashable을 만족해야합니다.
다음과 같이 선언하여 사용할 수 있습니다.
import IdentifiedCollections var standups: IdentifiedArrayOf<Standup> = []
가장 큰 장점은 특정 요소를 찾기 위해 firstIndex가 아닌 id를 사용해 시간복잡도 O(1)에 접근이 가능합니다.
standups[id: standup.id] = standup
Reducer도 다음과 같이 수정될 수 있습니다.
struct StandupsList: Reducer { struct State: Equatable { var standups: IdentifiedArrayOf<Standup> = [] } enum Action { case deleteStandup(id: Standup.ID) } func reduce(into state: inout State, action: Action) -> Effect<Action> { switch action { case let .deleteStandup(id): state.standups.remove(id: id) return .none } } }
성능?
- Github README에서는 성능이 Swift의 OrderedDictionary 성능과 일치한다고 주장합니다😃
어떻게 구현되어 있나?
- 내부적으로 Swift의 OrderedDictionary를 사용하여 래핑한 것을 살펴볼수 있습니다.
- OrderedDictionary는 순서를 보장하는 dictionary를 구현한 것으로 swift 오픈소스인 OrderedCollections에 내장되어 있습니다.
- Github(Link)
public struct IdentifiedArray<ID: Hashable, Element> {
public let id: KeyPath<Element, ID>
// NB: Captures identity access. Direct access to `Identifiable`'s `.id` property is faster than
// key path access.
@usableFromInline
var _id: (Element) -> ID
@usableFromInline
var _dictionary: OrderedDictionary<ID, Element>
/// A read-only collection view for the elements contained in this array, as an `Array`.
///
/// - Complexity: O(1)
@inlinable
@inline(__always)
public var elements: [Element] { self._dictionary.values.elements }
...
반응형
'Programming > iOS' 카테고리의 다른 글
메서드 스위즐링을 적용하여 실수로부터 벗어나기 (0) | 2025.03.17 |
---|---|
[번역] SwiftNIO Readme Conceptual Overview (0) | 2024.01.16 |
[iOS] Core Animation Basic (0) | 2023.09.30 |
[iOS] 객체 아카이빙과 iOS 12.0 이후의 변화 (0) | 2023.09.26 |
코코아 인터널스 - 9장 스위프트 타입 시스템 (0) | 2023.08.16 |