SwiftUI - TCA Side Effect의 에러 처리

CodeCat·2024년 9월 8일

IOS SwiftUI TCA

목록 보기
9/20
post-thumbnail

안녕하세요 !

이번에는 Side Effect의 에러 처리의 주제로 포스팅 해보려 합니다

주요 내용은 TCA에서 Side Effect의 에러를 어떻게 효과적으로 처리할 수 있는지 알아보는거에요

Effect.task를 사용한 기본적인 에러 처리 방법과 여러 Side Effect를 조합할 때 발생할 수 있는 에러 처리 상황을 다루어 볼 생각입니다

기본적인 에러 처리: Effect.task 사용

TCA에서 가장 기본적인 에러 처리 방법은 Effect.task를 사용하는 건데요

struct Feature: Reducer {
  struct State { var count = 0 }
  enum Action { 
    case incrementButtonTapped
    case decrementButtonTapped
    case incrementResponse(Result<Int, Error>)
  }
  
  func reduce(into state: inout State, action: Action) -> Effect<Action> {
    switch action {
    case .incrementButtonTapped:
      return .task {
        do {
          let newCount = try await increment(state.count)
          return .incrementResponse(.success(newCount))
        } catch {
          return .incrementResponse(.failure(error))
        }
      }
    case .decrementButtonTapped:
      state.count -= 1
      return .none
    case let .incrementResponse(.success(count)):
      state.count = count
      return .none
    case .incrementResponse(.failure):
      // 에러 처리 로직
      return .none
    }
  }
  
  func increment(_ count: Int) async throws -> Int {
    // 네트워크 요청 등의 작업 수행
    return count + 1
  }
}

이 예제에서 .incrementButtonTapped 액션이 발생하면, increment 함수를 호출하여 카운트를 증가시켜요

이 과정에서 발생할 수 있는 에러를 try-catch 구문으로 처리하고, 결과에 따라 적절한 .incrementResponse 액션을 반환해요 ㅎㅎ

(Effect.task는 비동기 작업을 수행하고 그 결과를 처리하는 데 사용됩니다)

즉 try-catch 블록 내에서 비동기 함수 increment를 호출하고, 성공 시 새로운 카운트 값을, 실패 시 에러를 포함한 액션을 반환하는거죠

여러 Side Effect 조합 시 에러 처리

때로는 여러 Side Effect를 조합해야 할 때가 있는데 이런 경우 각 Effect의 에러를 개별적으로 처리할 수 있습니다

struct Feature: Reducer {
  struct State { 
    var count = 0
    var name = ""
  }
  enum Action { 
    case loadButtonTapped
    case countLoaded(Result<Int, Error>)
    case nameLoaded(Result<String, Error>)
  }
  
  func reduce(into state: inout State, action: Action) -> Effect<Action> {
    switch action {
    case .loadButtonTapped:
      return .merge(
        .task {
          do {
            let count = try await loadCount()
            return .countLoaded(.success(count))
          } catch {
            return .countLoaded(.failure(error))
          }
        },
        .task {
          do {
            let name = try await loadName()
            return .nameLoaded(.success(name))
          } catch {
            return .nameLoaded(.failure(error))
          }
        }
      )
    case let .countLoaded(.success(count)):
      state.count = count
      return .none
    case .countLoaded(.failure):
      // 카운트 로드 실패 처리
      return .none
    case let .nameLoaded(.success(name)):
      state.name = name
      return .none
    case .nameLoaded(.failure):
      // 이름 로드 실패 처리
      return .none
    }
  }
  
  func loadCount() async throws -> Int {
    // 카운트 로드 로직
    return 10
  }
  
  func loadName() async throws -> String {
    // 이름 로드 로직
    return "John"
  }
}

위 예제는 .loadButtonTapped 액션이 발생했을 때 카운트와 이름을 동시에 로드해요

.merge를 사용하여 두 개의 Effect.task를 병렬로 실행하고 각 작업은 개별적으로 에러 처리를 수행해요

각 Effect.task 내에서 try-catch 구문을 사용하여 에러를 처리하는데 이렇게 하면 한 작업이 실패하더라도 다른 작업은 계속 진행될 수 있어요!!

예를 들어, 카운트 로드에 실패하더라도 이름 로드는 계속 진행될 수 있다는거죠

이 방법을 네트워크 통신할때 활용하면 되겠죠??

참고사항

TCA에서 Side Effect의 에러 처리는 주로 Effect.run을 사용하여 수행하고

단순한 경우에는 Effect.task를 사용해요 ㅋㅋㅋㅋㅋㅋ

그럼 굳이 Effect.task 왜 이걸 알려줬냐 하면 클로저가 단일 액션을 반환하는 간단한 경우에는 Effect.task를 권장하기 때문 입니다 !!

이상으로 포스팅 마무리 하겠습니다.

.
.
.

감사합니다.

profile
코드와 고양이의 만남

0개의 댓글