[TIL]04.21

rbw·2022년 4월 21일
0

TIL

목록 보기
6/97

SwiftUI의 데이터 흐름과 View 라이프 사이클

@StateObject

  • WWDC2020에서 추가된 내용
  • @ObservedObject와 거의 같은 방식으로 작동하는데, SwiftUI가 View를 다시 렌더링 할 때, 실수로 취소되는 것을 방지해준다.
struct ContentView: View {
    @StateObject var user = User()
}

@ObservableObject

  • 이는 프로토콜으로 Combine 프레임워크의 일부이다.
  • 이것을 사용하기 위해서는, 프로토콜을 준수하고 @Published를 사용하면 된다.
  • @Published를 사용하면 변수의 값이 추가되거나 삭제 되었다는 것을 View가 알 수 있게 해준다.
  • ObservableObject는 MVVM 아키텍처의 ViewModel에 적용하기 좋은 프로토콜이다.
class MyViewModel: ObservableObject {
    @Published var dataSource: MyModel
    
    init(dataSource: MyModel) {
        self.dataSource = dataSource
    }
}

@ObservedObject

  • @ObservedObject를 사용해 view가 외부 객체를 감지하게 해준다.
  • 아래 코드의 User class는 ObservableObject를 준수하고 @Published 변수를 가지고 있다.
  • SwiftUI는 user 객체의 @Published 변수 값이 변경될 때 view를 refresh 한다.
class User: ObservableObject {
    @Published var name = "Hohyeon Moon"
}

struct ContentView: View {
    @ObservedObject var user = User()

    var body: some View {
        VStack {
            Text("Your name is \(user.name).")
        }
    }
}

@environmentObject

  • 이는 보통 앱 전반에 걸쳐 공유되는 데이터에 사용된다.
  • .environmnetObject()를 통해 값을 전달 가능
  • 전달하는 object는 ObservableObject 프로콜을 준수해야한다.
  • 아래 코드와 같이 root view를 제공하면, 어떠한 view에서도 사용이 가능하다
// SceneDelegate.swift
var settings = UserSettings() 
window.rootViewController = UIHostingController(rootView: ContentView().environmentObject(settings))

onAppear(perform:)

view가 나타낼 때 수행할 action을 추가하고, 파라미터 perform에 들어가는 action은 optional 입니다.

.onAppear() // no effect
.onAppear(perform: {
    // action 
})

action이 nil이면 효과가 없습니다. 리턴값은 view가 나타날 때 action을 트리거한 view를 리턴합니다.

onDisappear(perform:)

view가 사라질 때 수행할 action을 추가하고, 파라미터 perform에 들어가는 action은 optional입니다.

.onDisappear() // no effect
.onDisappear(perform: {
    // action 
})

task(priority:_:)

iOS15에서 사용가능한 수정자입니다. 뷰가 나타날 때 수행할 비동기 작업을 추가합니다. (네트워크 요청을 넣을때 유용하게 사용한다)

예제 코드

var body: some View {
    List {
        // content
    }
    .task {
        self.viewModel.requestNames()
    }
}

task가 onAppear보다 좋은점은, task가 modifier로 추가된 view의 수명과 일치하는 수명을 가진다는 점입니다.

  1. task에 넣은 비동기 작업이 완료됨 -> View가 사라짐 -> 상관 x
  2. task에 넣은 비동기 작업이 완료되지 않음 -> View가 사라짐 -> SwiftUI가 작업을 취소해줌

기본적으로 task() 수정자를 사용하여 생성된 작업은 가능한 가장 높은 우선순위로 실행되지만, 작업의 중요도에 따라 우선순위를 사용자 정의로 적용할 수 있다.

task(id:priority:_:)

반환 값으로,id값이 변경된 작업을 다시 시작하는 뷰라는 점을 제외하면 위의 task와 동일하다.

위의 task와 다른점으로, 특정 값이 변경되면 SwiftUI는 이전 작업을 자동으로 취소하고 새값으로 새 작업을 생선한다는 차이도 있습니다. 변경을 감지하기 위해 수정자는 id 매개변수의 새 값이 이전값과 같은지 테스트합니다. 그러므로 이를 작동하기 위해 id는 Equatable 프로토콜을 준수해야한다.

var body: some View {
    NavigationView {
        List {
           // content
        }
        // 이 id값이 변결될 때마다 fetchData()를 실행한다.
        .task(id: selectedBox) {
            await fetchData()
        }
    }
}

onChange(of:perform:)

특정 값이 변경될때 action을 실행하고 싶으면 사용하면 된다.

  • 이를 사용하여 side-effect를 트리거 할 수 있다.
  • onChange는 메인스레드에서 호출된다 -> 긴 작업을 실행하는것을 삼가할 것
  • 값 변경에 대한 응답으로 긴 작업을 실행해야하는 경우 백그라운드 큐로 dispatch해야한다.

예제 코드

.onChange(of: self.text) { [text] (newText) in 
    print(text, newText) 
}

text는 oldValue, newText는 newValue라고 생각하면 된다. 위의 코드에서는 이전값과 새로운 값을 출력하는 모습.

SwiftUI - ForEach

일반적으로 ForEach를 사용하여 SwiftUI에서 View를 반복 할 수 있습니다. SwiftUI의 ForEach는 Struct구조 이므로 직접 반환이 가능합니다.

Form {
    ForEach(1..<10) { number in
        Text("Row \(number)") // number를 지우고 $0를 넣을수도 있다.
    }
}

배열의 각 요소를 고유하게 식별

id: \.self 부분은 SwfitUI가 배열의 각 요소를 고유하게 식별할 수 있도록 하기 위해 필요합니다. 즉, 항목을 추가하거나 제거하면 SwiftUI가 정확히 어떤 요소를 알고 있는지 확인 가능합니다.

List {
    Section(header: Text("first section")) {
        ForEach(arr1, id: \.self) { s in
            Text(s) // 값이 순차적으로 나타남
    }
}
// indices는 컬렉션을 오름차순으로 구독하는 유효한 인덱스입니다.
ForEach(people.indices) { index in
        Text("\(people[index])")
}

indices

배열의 인덱스를 안전하게 판단해주기 위해 사용함. Array를 확장해서 사용하는 모습

iOS9 이상 코드

extension Array {
    subscript (safe index: Int) -> Element? {
        return indices ~= index ? self[index] : nil    
    }
}

Clean Swift

MVVM 같은 아키텍처중 하나이다 다른이름으로 VIP(View, Interactor, Presenter)라고 부르기도 한다.

VIP 주기를 도입하였다, 이는 단방향 제어 흐름을 제공하고 시스템의 기초를 형성한다.

  • View : 뷰 (찾아보니 뷰 컨트롤러로 명시를 많이 하는듯 하다, 뷰 관련 로직 담당), 비즈니스 로직 관련은 Interactor에게 요청
  • Interactor : 비즈니스 로직처리, 요청된 것이 완료되면 Presenter에게 데이터 전송
  • Presenter : 데이터를 받아서 UI를 위한 모델 데이터로 가공, 가공된 모델을 View에게 전달

View -> Interactor -> Presenter -> View 흐름, 여기서 모든 곳이 Strong으로 연결이 되면 순환 참조가 일어나기 떄문에 Presenter에서는 View로 가는 흐름은 Weak로 가진다.

View는 Interactor에 있는 비즈니스 로직 호출을 할 때 Request에 필요한 데이터를 담아 넘겨준다. Interactor는 필요한 로직을 처리한 후, Presenter에게 그 결과값을 Response에 담아서 전송한다. Presenter는 View가 화면에 정상적으로 값을 표현할 수 있도록 Response의 데이터를 가공해 ViewModel에 담아 넘겨준다.


참조

https://www.hohyeonmoon.com/blog/swiftui-data-flow/

https://zeddios.tistory.com/1306

https://zeddios.tistory.com/1154

https://seons-dev.tistory.com/33

http://minsone.github.io/programming/check-index-of-array-in-swift

https://tv.naver.com/v/4980400/list/267189

profile
hi there 👋

0개의 댓글