[SwiftUI][concept][개념] ForEach 에 대하여

Uno·2021년 12월 8일
0

SwiftUI

목록 보기
10/30

작성하게된 동기

  • SwiftUI를 공부하다보면, ForEach를 통해서 UIKit에서 구현하던 UICollectionView나 UITableView를 구현하게 됩니다.
  • UIKit에서도 ForEach를 사용하긴 했었지만, 그 당시에는 단순히 For문의 축약형 정도로만 사용했던 것 같습니다.
  • SwiftUI에서는 초기화 함수 모형부터 다른 ForEach에 대해서 궁금해졌고, 조사하게 되었습니다.

공식문서에서 ForEach

애플 공식문서 링크: Apple Developer Documentation

애플 공식문서에서는 다음과 같이 설명되어 있습니다.

A structure that computes views on demand from an underlying collection of identified data.
-> 식별된 데이터의 기본 컬렉션에서 요청 시, 뷰를 계산하는 데이터구조체입니다.
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public struct ForEach<Data, ID, Content> where Data : RandomAccessCollection, ID : Hashable {

    /// The collection of underlying identified data that SwiftUI uses to create
    /// views dynamically.
    public var data: Data

    /// A function to create content on demand using the underlying data.
    public var content: (Data.Element) -> Content
}

설명과 정의된 걸 보니, 제가 알던 고차함수의 ForEach과 1:1 매칭이 되는 개념은 아니였네요.
아직까지는 위 정의가 와닿지 않습니다. 좀 더 공식문서를 읽어볼게요.

Use ForEach to provide views based on a RandomAccessCollection of some data type. Either the collection’s elements must conform to Identifiable or you need to provide an id parameter to the ForEach initializer.

-> ForEach를 뷰를 제공할 때, 사용합니다. 그런데 그 뷰는 “RandomAccessCollection”의 데이터 타입인 뷰 입니다. 컬렉션의 구성요소들은 Identifiable 을 준수하고 파라미터로 id값을 반드시 주어야 합니다.

음. 뷰를 만들 때 사용하는구나. 그리고 궁금한 점이 생겼습니다.

“RandomAccessCollection” 이녀석만 알면 좀 이해가 될 것 같습니다.

RandomAccessCollection

protocol RandomAccessCollection where Self.Indices : RandomAccessCollection, Self.SubSequence : RandomAccessCollection

음 이친구는 프로토콜이고, 랜덤한 인덱스를 순회하는데 효율적으로 할 수 있도록 도와주는 컬렉션 프로토콜이라고 합니다.
(애플이 잘 만들었으니 가져다 쓰기만 해. 뭐 이런 느낌의 프로토콜)

내용은 이번 글의 주제와 벗어나서 작성하진 않지만, 내용에 흥미로운 내용이 많습니다.

보통 순회를 하게되면 O(n) 만큼 시간이 걸리는데, 이 친구는 어떤 일을 했는지 모르겠는데, O(1) 로 어떤 거리의 인덱스로든 이동이 가능하다고 하네요. 궁금하신 분들은 한 번 읽어보시는걸 추천드리겠습니다.

다시 본론으로 돌아와서 ForEach에 대해서 정리해보겠습니다.

ForEach

  • For문과 동일하게 순회하면서 로직을 실행시키는데, Content를 가지고 있다.
  • Identifiable 을 통해서 각각의 Content를 구분해줘야만 사용 가능하다.
  • RandonAccessCollection 이라는 프로토콜을 통해 성능향상이 기대할 수 있다.

그러면 애플이 제시한 예시를 한 번 보겠습니다.

애플이 제공한 예시

private struct NamedFont: Identifiable {
    let name: String
    let font: Font
    var id: String { name }
}

private let namedFonts: [NamedFont] = [
    NamedFont(name: "Large Title", font: .largeTitle),
    NamedFont(name: "Title", font: .title),
    NamedFont(name: "Headline", font: .headline),
    NamedFont(name: "Body", font: .body),
    NamedFont(name: "Caption", font: .caption)
]

var body: some View {
    ForEach(namedFonts) { namedFont in
        Text(namedFont.name)
            .font(namedFont.font)
    }
}

먼저 모델 선언을 보겠습니다.

private struct NamedFont: Identifiable {
    let name: String
    let font: Font
    var id: String { name }
}
  • Identifiable 을 채택한 구조체입니다. 동시에, id 라는 변수를 설정하고 그 연산프로퍼티는 name을 리턴하도록 했습니다. 이를 통해서 name이라는 상수가 id값으로 활용 가능하겠네요.

구조체타입을 바탕으로 해당 타입으로 배열을 선언하여 값을 할당합니다.

private let namedFonts: [NamedFont] = [
    NamedFont(name: "Large Title", font: .largeTitle),
    NamedFont(name: "Title", font: .title),
    NamedFont(name: "Headline", font: .headline),
    NamedFont(name: "Body", font: .body),
    NamedFont(name: "Caption", font: .caption)
]

마지막으로 실제 UI에는 어떻게 적용되었는지 보겠습니다.

var body: some View {
    ForEach(namedFonts) { namedFont in
        Text(namedFont.name)
            .font(namedFont.font)
    }
}
  • ForEach(값) { 각각의값 in } 이런 형식으로 구성되어 있네요.
  • 어??? id 값은 어디갔지?? 라고 생각이 되었습니다.
  • 없어도 되는 이유는 Identifiable 을 채택하고 있는 구조체이기 떄문에 가능합니다.
  • 만약 채택하지 않았으면 다음과 같이 작성해야합니다.
var body: some View {
    ForEach(namedFonts, id: \.self) { namedFont in
        Text(namedFont.name)
            .font(namedFont.font)
    }
}

정리

처음에는 익숙하지 않지만, 이 ForEach 문법 덕분에 SwiftUI 의 많은 코드들이 깔끔해지고 편리하게 작성이 가능하더라구요. (UITableViewDelegate ㅃ2)

UIKit에서 UITableView, UICollectionView를 사용할 때, 주로 ForEach 위 문법이 사용됨을 기억하시면, 언제 사용하시면 될지 감이 점점 잡히실겁니다.^^

읽어주셔서 감사합니다~!

참고자료

profile
iOS & Flutter

0개의 댓글