이전글
2026.01.19 - [iOS] - TCA 튜토리얼 따라해보는중 01-Your first feature
01에 이어 이번엔 부작용을 추가해본다는데....
솔직히 이건 사이드 이펙트라기보단 그냥 제공해주는 리듀서의 반환타입에 대해 알아보아요 이렇게 하면 async를 쓸수있어요! 라는 쳅터다
일단 이번에 추가한건 api사용에 대한 뷰 업데이트,
그리고 타이머의 사용이다
우선 1. api + async의 사용
api 사용을 위해 isLoading 상태를 추가하고 fact 상태로 받아서 뷰를 업데이트한다
isLoading값은 뷰에서 프로그레스바를 보이고 가리고 역할을 해줄거고
await을 통해 isLoading의 업데이트를 api값을 반환이후로 받아준다.
참고로 이번에 사용하는 api는 공식 튜토리얼에서 소개해주는 api주소가 아니다.(기존 주소는 동작하지 않는중)
http://www.number-trivia.com/{number}로 해줬는데 이게 원래 같은곳에서 주소만 바뀐건지... 아니면 다른사람이 다시 같은 기능을 제공해주는지 모르겠으나 거의 같은걸 지원해주니 이걸 이용하면 된다.
아무튼 그 기능을 await를 이용해 리듀서 내부에서 사용하려는데 이건 async 를 명시한 함수에서만 사용가능해서 사용을 못한다
이걸 하기위해 반환값이 run으로 해주는데 기존에 해줬떤건 전부 none에서 새로운걸 다뤄보는 느낌
참고로 반환은 아래코드대로 3가지로 보인다.
enum Operation: Sendable {
case none
case publisher(AnyPublisher<Action, Never>)
case run(
name: String? = nil,
priority: TaskPriority? = nil,
operation: @Sendable (_ send: Send<Action>) async -> Void
)
}
아마 다음 챕터에서 publisher를 다뤄보지않을까?
아 또 이전거 소스에서 달라진점이 하나있는데
- var body: some ReducerOf<Self> {
+ var body: some Reducer<State, Action>{
원래는 윗줄 대로 REducerOf로 해야하는게 최신스타일 같으나 난 이상하게 오늘도 빌드가 되지않는다.
버전문젠가 해서 여러가지로 테스트해봐도 문제가 발생해서 그냥 이전처럼 할까하다가 ReducerOf의 설명문에 이거 대신 reducerOf사용하세요! 라고 적힌걸 봐서 그거로 테스트해보니 이전보다 훨씬 보기좋게 가능해졌다!
또 튜토리얼과 다른부분은
이 CancelID부분인데 'CounterFeature.CancelID' to 'Hashable' cannot satisfy conformance requirement for a 'Sendable' type parameter 라는 빌드에러로 빌드가 되지않아 수정해줬다.
nonisolated enum CancelID: Hashable, Sendable {
case timer
}
이 부분은 아마 swift가 버전업되면서 안정성을 더 보장하기 위해 업데이트된 부분이 아닌가 싶은데...
설명을 읽어봐도 메인액터와 격리에 대한게 바로 이해가 되지않아 이건 내일로 미룬다.
그렇게 아래 소스! 뷰는 튜토리얼과 다르지않아 그냥 냅둔다
import ComposableArchitecture
import Foundation
@Reducer
struct CounterFeature {
@ObservableState
struct State {
var count = 0
var isLoading = false
var fact: String? = nil
var isTimerRunning = false
}
enum Action {
case decrementButtonTapped
case incrementButtonTapped
case factButtonTapped
case factResponse(String)
case toggleTimerButtonTapped
case timerTick
}
nonisolated enum CancelID: Hashable, Sendable {
case timer
}
// var body: some ReducerOf<Self> {
var body: some Reducer<State, Action>{
Reduce { state, action in
switch action {
case .decrementButtonTapped:
state.count -= 1
return .none
case .incrementButtonTapped:
state.count += 1
return .none
case .factButtonTapped:
state.fact = nil
state.isLoading = true
return .run { [count = state.count] send in
let (data, _) = try await URLSession.shared
.data(from: URL(string: "http://www.number-trivia.com/\(count)")!)
let fact = String(decoding: data, as: UTF8.self)
await send(.factResponse(fact))
}
case let .factResponse(fact):
state.fact = fact
state.isLoading = false
return .none
case .toggleTimerButtonTapped:
state.isTimerRunning.toggle()
if state.isTimerRunning {
return .run { send in
while true {
try await Task.sleep(for: .seconds(1))
await send(.timerTick)
}
}
.cancellable(id: CancelID.timer)
} else {
return .cancel(id: CancelID.timer)
}
case .timerTick:
state.count += 1
state.fact = nil
return .none
}// switch
}// body
}
}
https://github.com/rithmschool/numbers_api
GitHub - rithmschool/numbers_api: API for querying interesting information about different numbers
API for querying interesting information about different numbers - rithmschool/numbers_api
github.com
https://docs.swift.org/compiler/documentation/diagnostics/isolated-conformances/
Documentation
docs.swift.org
'iOS' 카테고리의 다른 글
| TCA 튜토리얼 따라해보는중 01-Your first feature (0) | 2026.01.19 |
|---|---|
| 애플 앱결제 외부결제 허용? 이제 30%수수료 안내도 되는가 (0) | 2025.05.01 |
| 앱스토어 상단 이미지는 어떻게 넣나요? (0) | 2022.04.18 |
| 앱의 민감한 정보를 보호하는 방법 (0) | 2022.04.07 |
| The linked library 'libPods-ProjectName.a' is missing one or more architectures required by this target: x86_64 (0) | 2021.07.12 |