OverviewPermalink

문득 iOS를 좀 더 공부해보고 싶었고, 아키텍쳐적으로 공부를 진행해보기로 했다.

가장 처음으로 파고든건 tuist였고, 이것에 대한 공부는 성공적으로 마쳤다. 독스들을 보고 실제 프로젝트를 이용해 리팩토링을 해보고 tuist helper 구조도 짜봄으로써 The Modular Architecture(TMA)를 적용해보았다.

도움이 되었던 프로젝트 샘플은 Pumping-iOS였다.

StudyPermalink

Tuist의 TMA를 이해하며 실제로 Swift에서 이걸 어떻게 구현을 하지? 하고 생각을 많이 했고 위 샘플 프로젝트가 많이 도움이 되었다.

Swift의 Protocol은 기본적으로 Protocol Oriented Programming이란 개념으로, OOP의 Interface와 개념이 조금 다르다.

개념이 조금 다른건 차치하고, Swift의 Protocol과 Opaque(Existential), Boxed Generic System은 DX가 그리 좋지 못했다.

Interface module에 UserStore를 Protocol로 정의하고 Implementation Module에 DefaultUserStore를 정의한다고 했을 때, Dependency Inversion을 구현하는 과정에서 애로사항이 생긴다.

Protocol을 주입하려면 이것의 타입은 any UserStore 가 되어야 하는데, 이것이 Observable 이 되어야 하거나 ObservableObject가 되어야 할 때, UserStore에서 그것들을 conform한다 해도 Swift의 언어적 한계로인해 Observable이 필요한 자리에 any UserStore를 넣을 수 없다.

protocol B { func foo() }
protocol A: B { func bar() }

let a: any A = ConcreteA()
let b: any B = a  // (실제론 OK) → Swift는 ❌ 컴파일 에러

위 예시를 보면, any A 타입을 any B 타입으로 변환할 수 없다는 것을 알 수 있다. 이는 Swift의 Existential Type System이 Protocol을 상속하는 경우에도 타입 변환을 허용하지 않기 때문이다.

즉, Existential Type들 간에 covariance가 없다.

이는 Protocol을 OOP의 Interface로 사용한다는 것이 문제가 생겼고, 이것에 대한 해답은 구조체를 이용한 추상화였다.

Swift의 구조체는 생성자를 정의하지 않으면 자동으로 생성자를 만들기 때문에 이를 구현하는 static 한 멤버들을 extension 하는 것으로 추상화를 구현할 수 있다. 그리고 이것이 TCA에서 추천하는 방식이다.

// interface
struct UserStore {
  let fetchUser: () -> User
}

// Implementation
extension UserStore {
  static let live: Self = .init(fetchUser: {
    User(name: "John Doe")
  })
}

TCA(The Composable Architecture)Permalink

그래서 난 TCA의 Tutorial들을 보고 Documentation을 보고있다.

난 TCA가 Redux 같은 상태관리에 국한된 라이브러리인줄 알았는데, 천천히 살펴보니 그것보다 많은 일들을 해줄 수 있고 큰 프로젝트 구조에서 더 구조적이고 Testable한 구조를 만들어준다는 것을 알게 되었다.

사실 내가 만들고있는 앱에서는 TMA, TCA는 필요없지만, 원래 잘 알고있어야 쓸지 말지도 결정할 수 있기 때문에 공부해서 적용해보기로 했다.

Categories:

Updated:

Comments