[SwiftUI] Date Planner 제작기 3

Valse·2022년 11월 2일
1

DatePlannerDiary

목록 보기
3/3

Date Planner

Date Planner 라는 플레이그라운드 앱이 있습니다.
고 녀석을 만드는 중인데 일주일이 코앞에 다가오네요.
놀면서 하는 것 같지는 않은데 생각보다 속도가 안나서 속상..


오늘은 뭘 했는가?

보기 좋은 코드에 대한 고민

  • 보기 좋은 코드는 협력 효율을 높이고 코드 가독성도 높여준다.
    그래서 나는 보기 좋은 코드를 쓰려고 꽤 노력하는 편인데, 그래서 그런가 속도가 나지 않을 때가 있다.

  • 예컨대 @ViewBuilder를 쓰면 코드 자체의 가독성은 매우 좋아지지만 그 내부로 어떤 데이터를 전달해야 한다면 파라미터 타입을 고민해야 한다.
    Any View, Binding<Optional> 등등 나를 괴롭히던 요상한 타입들이 너무 많았고 어찌어찌 해결은 했다.

  • 아니면 내가 아직 코드에 익숙하지 않은 것일 수도 있다.
    코드를 보면서 고민을 더 오래 하다보면 더 쉬운 코드가 생각나곤 했으니까.
    며칠 전에 올렸던 sectionHeaderMaker 뷰빌더는 Any 타입을 받고 타입캐스팅 하는 등 나름 독창적인 해결책을 쓰긴 했어도 그 쓰임 자체가 SwiftUI 표준에 걸맞지는 않다.
    SwiftUI에는 뷰 프로토콜 구조체를 위해서 Any View, some View 등의 오팩 타입이 존재하기 때문에 Any를 쓸 일은 없다.

  • 그 이상한 코드는 아래처럼 변했고, 내가 필요로 하는 쓰임을 제대로 갖추고 있다.
    섹션 헤더가 ForEach 안에서 돌지 않아야 하며, 유동적으로 새로운 이벤트 뷰를 만들 수 있어야 하고 마지막으로 내용을 셀에 전달할 수 있어야 했다.

@ViewBuilder
	private func sectionHeaderMaker(headerTitle: String, startDay: Int, endDay: Int) -> some View {
		let arr = Array(eventEnvManager.allEventDictionary.keys.enumerated())
		Section {
			ForEach(arr, id: \.element) { _, keys in
				if let eachEvent = eventEnvManager.allEventDictionary[keys]! {
					switch eachEvent.timeDiff {
					case startDay ... endDay:
						NavigationLink(destination: {
							let _ = print("nav :" + "\(eachEvent)")
							MakeNewEventView(isNewEventViewShowing: $isNewEventViewShowing,
											 eachEvent: eachEvent)
						}, label: {
							EventCellView(eachStruct: eachEvent, reloadToggle: $reloadToggle)
						})
					default:
						EmptyView()
					}
				}
			}
		} header: {
			Text(headerTitle)
		}
	}

생성자와 옵셔널 이야기(feat. @Binding)

  • 바인딩 프로퍼티 래퍼는 그 내부에 옵셔널을 받지 않는다.
    당연한 이야기지만 프로퍼티 래퍼로 감싸진 Binding<Optional> 변수는 그 뒤에서 옵셔널 체이닝하더라도 Binding<non-Optional> 타입을 받아올 수 없다.

  • 다 좋은데.. 옵셔널 @State 변수를 바인딩해야 할 때가 문제였다.
    애시당초 그런 형태의 바인딩은 API로 없었기 때문에 스택오버플로우를 방황해야 했다.

  • 그러나.. 내가 원하는 해결책은 구하지 못했다.
    ?? 연산자를 오버로딩하거나 옵셔널 타입을 확장하거나 하는 등등의 경우의 수가 내가 원하는 기능에서 맞아떨어지지 않았다.

  • 나는 결국.. 옵셔널을 버려야 했고 불필요한 위치에서 구조체를 불필요하게 생성해야 했다.
    이 구조체는 앱이 실행되면서 처음으로 생기는 구조체가 되었는데, 이 구조체로 뭔가 할 수 있다면 해볼 생각이다.


데이터의 휘발과 프로퍼티 래퍼

  • @Binding 은 값을 갖고 있지 않다.
    그래서 외부에서 하위 뷰의 Binding에 직접 접근해봤자 값을 가져올 수 없다.
    오직 @State 를 포함한 Source of Truth 속성의 프로퍼티 래퍼들만이 실제 값을 갖는다.

  • 며칠 전에 쓴 글에서 데이터의 휘발과 전달에 대해 고민한 적이 있는데, 내가 아직 프로퍼티 래퍼에 대한 이해가 부족해서 생긴 현상으로 보인다.
    고민했던 흔적이 정답과 가까운 건.. 호재인가?

  • 그래서 데이터를 직접 보관하다가 전달해야 하는 뷰에서는 @State, @StateObject로 코드를 변경했다.
    동작은 하는데 조금 더 기능을 추가하다가 보면 뭔가 다시 손대야 할 것 같다.


아키텍쳐의 소중함

  • 지금 만들고 있는 이 Date Planner 앱은 아키텍쳐를 갖고 있지 않다.
    뷰모델이 없으며 모델도 사실상 이름만 모델이다.
    하나의 중앙 모델이 있는 것도 아니며, 분산 모델들도 서로 제대로 된 커뮤니케이션 허브를 갖고 있지 않다.

  • 데이터의 흐름과 뷰가 어떻게 반영되는지를 중점적으로 설계하고 앱을 만들기 시작해야 한다는 생각을 뼈저리게 하고 있다.
    설계에 대한 내 이해도 부족에서 오는 어려움이라 생각하는데, 애초에 뭐가 뷰 모델이 되어야 하고 뭐가 뷰에 있어야 하는지를 잘 모르기 때문인 것 같다.

  • UIKit을 할 때랑은 완전히 다른 아키텍쳐 구성이나 데이터 소통이 아직까지 내게 어려운 듯하다.
    UIKit에선 내 마음대로 컴플리션 핸들러도 써 가면서 데이터 옮기고 그랬는데 SwiftUI는 프로퍼티 래퍼가 따로 있고..

  • 다음 앱을 제작하게 된다면 데이터 흐름을 중점적으로 고민하고 계획해서 더 깔끔하게 코딩에 착수해야겠다.
    지금은 너무 중구난방으로 코딩하고 있다는 생각이 머리에서 떠나질 않는다.

이게 대체 뭐냐

배운 점들

  • 나는 코딩을 시작하기 전에 고민이 없는 편이다.
    제대로 계획하고, 데이터 흐름을 이해하고 코딩을 시작하자.
    괜히 삽질하고 스파게티만 만들게 된다.

221103

profile
🦶🏻🦉(발새 아님)

0개의 댓글