저번편에 이어 케어밋 프로젝트 회고를 마저 작성해보려고 한다.
GitHub - 3IDLES/idle-iOS: SW Maestro 15th | 재가요양 시장의 새로운 구인구직 플랫폼 | 케어밋
SW Maestro 15th | 재가요양 시장의 새로운 구인구직 플랫폼 | 케어밋. Contribute to 3IDLES/idle-iOS development by creating an account on GitHub.
github.com
의도를 잘 못 파악한 MVVM패턴 사용 🙃
해당 프로젝트는 MVVM패턴을 사용하여 UI와 데이터와 로직 각기다른 객체로 분리했다.
UI는 말그대로 User Interface로써 데이터를 기반으로 시각화 정보 및 유저로부터 액션을 전달받는다.
데이터와 로직은 시각화할 정보와 특정 유저 액션 시 실행될 내부 로직이다.
두개를 분리하면 당연하게도 UI재사용성이 크게 증가한다.
이 부분에서 크게 간과한 것은 뷰컨트롤러의 재사용성이다.
MVVM은 Model View ViewModel이다.
여기서 View에 해당하는 것은 UIView와 UIViewController라고 생각할 수 있다.
???: 추상화된 뷰모델을 뷰컨트롤러에 바인딩시키면 뷰모델에 따라 완전히 다른 동작을 유도할 수 있지 않을까?!?!
그런데 정말 뷰컨트롤러를 재사용할 수 있을까? 솔찍히 쉽지 않다.
어떻게,,, ViewModel을 인터페이스화해서 의존관계 느슨하게 할 수 있지만,
뷰컨트롤러는 대부분 존재 자체적으로 너무 덩치가 크고 구체적이다.
케어밋 프로젝트를 보면 대부분의 ViewModel이 추상화되어 있고 뷰컨트롤러는 추상화 타입을 바인딩한다.
하지만, 하나의 뷰모델 인터페이스에 2개이상의 구현체를 가진 ViewModel은 거의 없었으며
있다해도 뷰컨트롤러를 손도 못댈 정도로 재사용되는 영역에 커플링되었다. 즉, 수정이 거의 불가한 상태이다.
결론적으로 재사용의 단위가 되야하는 것은 뷰컨트롤러가 아니라 UIView이다.
. . .
그리고 재사용되면서 변동성이 거의 없는 UI가 아니라면, 아무리 여러곳에서 재사용된다고 해도 공유 모듈에 위치시키지 않는 것이 좋다.
???: UI는 디자인을 반영한 것이니.. 모든 컴포넌트는 DSKit모듈에 위치시키자!
케어밋 프로젝트에서는 흔히 UIView를 상속받은 하위타입들을 컴포넌트로 규정짓고 대부분을 DSKit모듈에 위치시켰다.
재사용가능할 것이라고 생각했기 때문이다.
하지만, 생각보다 재사용되지 못하거나 수정사항이 자주생겨 모든 모듈의 빌드에 영향을 끼쳐버리는 참사가 일어났다.
어떤 현업 개발자가 말했다. 코드 삭제가 수정보다 빠르면 중복코드를 작성하라고,, 이말에 어느정도 동의하게 되었다.
상속을 통한 UI재사용시 단점 🙃
다수의 화면에서 사용되는 UI는 상속하면 안될까?
???: 상속을 사용해서 모든 ViewController가 해당 UI를 프로퍼티로 상속하게 하면되겠군!
상속의 단점에 대해 한번 떠올려보자
앞서언급한 MVVM의 문제와 비슷하다. 수정에 취약해진다.
만약에 디자인이 변경되어 특정 화면에는 다른 스타일의 UI가 적용된다고 해보자.
그러면 부모 객체에 분기문을 추가해야한다.
몇번은 간단한 분기문으로 처리할 수 있지만, 분기문이 쌓여가면 해당 코드의 확장성은 엄청나게 감소하며 가독성마자 나빠진다.
즉, 지속가능한 코드라고 볼 수 없어진다.
어떤 현업개발자가 그랬다,,, 상속은 때려 죽여도 안변하는 것들에 대해서만 사용하는 거라고..😇
상속에 대한 여담
학부시절 OOP를 처음배울때 상속의 개념에 대해 처음 접할 수 있었다.
중복코드를 줄일 수 있는 강력한 프로그래밍 방법이라고 생각한다.
UI코드 작성의 최신 트랜드를 살펴보자, 바로 선언형 UI이다.
SwiftUI와 UIKit의 상속의 유무에서 큰 차이점을 가진다.
UIViewController는 상속을 통해 라이프 사이클을 제어한다.
SwiftUI의 View는 라이프 사이클 관련 이벤트를 사용하려면 Modifier를 부착하여 View의 기능을 확장해야한다.
전자는 수직적 확장을 후자는 수평적 확장을 말한다고 볼 수 있다.
수직적 확장은 이 글에서 언급한 것처럼, 변화에 취약하다.
물론 UIViewController는 잘 만들어져 있어, 아직까지 잘 사용되고 있지만, 불편한 점이 많다.
예를들어 라이프 사이클 이벤트에 대해 뷰마다 각기다른 처리가 필요할 경우,
UIViewController의 viewDidLoad에서 분기문을 통한 처리가 필요하다.
반면 SwiftUI는 뷰마다 각각의 Modifier를 부착해 확장해주면된다.
개발자입장에서 고려해야할 것이 분산되고 코드는 직관적이게 된다. 😎
func viewDidLoad() {
super.viewDidLoad()
if ... {
// AView
} else {
// BView
}
}
AView()
.someModifiers()
.onAppear { ... }
BView()
.someModifiers()
.onAppear { ... }
AView가 더이상 해당 이벤트가 필요하지 않을 경우 AView의 Modifier만 지워주면된다.
반면 viewDidLoad는... 벌써 어지럽다..🤯
RxSwift, Combine도 이런 철학을 잘 반영한다고 생각한다.
해당 프레임워크에서는 Publisher, Observable과 같은 프로토콜을 상속하거나 구현체를 구현하여 자신만의 비동기처리 타입을 만들 수 있다.
하지만 공식문서에도 권장하지 않으며, 베이스가 되는 타입들을 Operator로 확장하여 원하는 기능을 충분히 이끌어낼 수 있다.
. . .
물론 수평적 확장이 항상 수직적 확장보다 좋은 것은 아니다.
오히려 상속을 통해 가려진 기능들을 직접 구현해야할 수 있다는 점에서 코드량이 늘어날 수 있다.
총평
재사용가능한 UI란 결국 데이터나 로직과 잘 분리된 UIView의 하위타입인 것 같다
물론 복잡하지 않은 ViewController도 포함할 수 있을 것 같다.
. . .
"재사용" 참 듣기좋은 말이지만, 위함한 말이라는 생각도 이제 든다.
규모가 큰 프로젝트일 수록 이러한 부분은 더욱 충분한 고려가 필요할 것이라 생각된다.
재사용,, 정말 중요하지만 항상 경계하자
'iOS공통' 카테고리의 다른 글
[iOS] 나만의 이미지 캐싱 SDK만들기(다운샘플링, 디스크 & 메모리 캐싱) (0) | 2024.11.22 |
---|---|
[iOS] Fun하지 않은 정적/동적 라이브러리와 Symbol (3) | 2024.10.31 |
[iOS] 케어밋 프로젝트 기술회고1: 클린아키텍처 (4) | 2024.10.10 |
[iOS] 첫 어플리케이션 배포 심사 회고 (1) | 2024.09.26 |
[Swift] 이미지 캐싱을 통한 로딩속도 최적화 (0) | 2024.09.23 |