맛동산이

SwiftUI) TCA- Effect에 대해서 본문

앱/SwiftUI

SwiftUI) TCA- Effect에 대해서

진ddang 2024. 9. 17. 04:42

Effect는 Action을 통해서 동작할 동작들을 의미한다.

Effect에는 다음과 같은 동작들이 존재한다 .

  • none: 즉시 완료
  • run: 비동기 작업
  • send: 전달된 Action을 즉시 내보냄
  • cancel: 진행중인 Effect를 취소
  • merge: 동시에 실행되는 단일 Effect로 병합
  • concatenate: 차례로 실행되는 단일 Effect로 연결
  • animation: SwiftUI의 .withAnimation
  • transcation: SwiftUI의 .withTransaction
  • debounce: debounce Effect로 변경
  • throttle: trottle Effect로 변경

Effect는 기본적으로 단일의 행동을 하도록 하고, 여러 동작을 묶을수도 있으며, 연쇄적으로 동작할수도 있다.

예시 )

Reduce { state, action in
    switch action {
    case .onAppear:
        return .concatenate([
            .send(.request)
        ])
    case .request:
        return .run { send in
            let response = await newsClient.request()
            await send(.fetchResponse(response))
        }
        
    case let .fetchResponse(.success(newses)):
        state.newses = newses
        return .none
        
    default:
        return .none
}

Concatenate와 같은 타입 사용법

기존의 .none과 .run처럼 두개의 액션을 동시에 처리하거나, 순서대로 처리하거나 하는 동작에서

.merge와 .concat과 같은 방식으로 처리를 할수 있음

예시코드

Reduce { state, action in
    switch action {
    case .onAppear:
        return .concatenate([
            .send(.someActionFirst)
            .send(.someActionAfter) 
        ])
}

액션에 따른 Effect를 묶어주고, 해당 이펙트들이 다시 액션에서 동작하도록 묶어주는 역할을 하게 된다.

이를 통해서 api호출의 순서를 보장하거나, UI 업데이트의 순서를 보장하거나 할 수 있다.

자주 보는 에러

run 안에서는 state를 캡처링 할수 없기 때문에

  case .factButtonTapped:
    state.fact = nil
    state.isLoading = true

    let (data, _) = try await URLSession.shared
      .data(from: URL(string: "<http://numbersapi.com/\\\\(state.count)>")!)
    // 🛑 'async' call in a function that does not support concurrency
    // 🛑 Errors thrown from here are not handled

    state.fact = String(decoding: data, as: UTF8.self)
    state.isLoading = false

    return .none

이렇게 직접적으로 state의 값을 변경하려고 하면 에러가 발생한다 .

따라서 새로이 action을 정의하고, 해당 액션 안에서 받은 데이터를 통해서 State를 다시 업데이트 해주는 방식으로 처리해야한다.

해결방법 코드

  case .factButtonTapped:
    state.fact = nil
    state.isLoading = true
		return .run { send in 
	    let (data, _) = try await URLSession.shared
	      .data(from: URL(string: "<http://numbersapi.com/\\\\(state.count)>")!)
	    let fact = String(decoding: data, as: UTF8.self)
	    send(.fetchData(fact)
	  }
   
  case let .fetchData(fact):
	   state.fact = fact
     state.isLoading = false
     return .none
반응형