MVVM Observable 구현
MVVM은 Observable을 이용해 구현했습니다.
RxSwift, Combine도 있지만 일단 가장 기본적인 방법으로 구현해보고 싶었습니다.
프로퍼티 옵저버를 이용해 value가 변경되면 등록한 리스너를 실행합니다.
이렇게 MVVM의 데이터 바인딩을 구현하였습니다.
추후 RxSwift나 Combine을 이용해 리팩토링을 할건데 둘 중 무엇을 선택할지는 그때 상황을 봐야할 거 같네요.
웬만하면 프레임워크인 Combine을 선택하지 않을까 싶습니다.
HttpError 구현
Http 에러 상황에 따라 catch를 다르게 하면 예외처리를 좀 더 세부적으로 할 수 있겠다고 생각했습니다.
그래서 HttpError라는 열거형을 구현했어요.
근데 명확하게 구현하는 게 무척 어렵더라고요. 고수님들 댓글로 많은 가르침 부탁드립니다 ㅎㅎ...
serverStateError는 서버 점검 등의 이유로 이상이 있을 때 공지를 내려주는 케이스입니다.
런치 화면에서 serverStateError가 발생하면 notice를 Alert에 보여줘요.
statusCodeError는 HTTP Status가 200이 아닐 때 발생합니다.
urlError는 URL을 생성하지 못했을 때 발생하고,
jsonError는 json 인코딩, 디코딩에 실패했을 때 발생합니다.
apiError는 response의 result가 ok가 아닐 때 발생하는데 fail일 경우 catch에서 상황에 맞는 처리를 했습니다.
summarizeError는 Summarize API의 response result가 ok가 아닐 때 발생하는데 에러가 발생한 이유를 response의 message로 전달 받아서 Alert으로 알려줍니다.
사용자 입장에서 그냥 "문제가 발생했습니다."로 안내하는 것보다 세부적인 이유를 안내하면 사용성이 증가할 거 같았기 때문에 이렇게 구현했습니다.
근데 이는 apiError와 역할이 겹치는 거 같아 구현하면서도 이게 맞나..? 싶었어요 ㅠㅠ
좀 더 좋은 방법이 있는지 고민해 봐야겠습니다.
Launch 화면(수정 전)
런치화면에서는 버전 비교를 하게 됩니다.
서버에 등록된 최신 버전과 현재 설치된 버전을 비교해서 표시해 줍니다.
런치 VC의 프로퍼티는 런치 VM이 유일합니다.
viewDidLoad
viewDidLoad도 메서드를 호출하는 것으로 코드 길이를 줄였어요.
launch
근데 launch 메서드가 좀 애매한 거 같습니다.
어떻게 작성해야 깔끔할지 아직까진 막막하더라고요.
ViewModel에서 서버 상태와 버전을 체크하고 Error가 throw 되지 않으면 홈 화면으로 이동합니다.
check 메서드
ViewModel에 있는 checkState와 checkVersion 메서드는 이렇게 생겼어요.
각자의 api를 호출하고, response의 result를 체크한 뒤 json 데이터를 파싱합니다.
json 데이터를 파싱할 때 실패하게 되면 jsonError를 발생시키고, 문제가 없다면 JSONSerialization을 이용해 파싱합니다.
고민 중
그래서 ViewController의 메서드는 luanch와 goHomeVC가 전부인데요.
근데 launch와 goHomeVC는 ViewController에 있는게 맞을지, View Model에 있는게 맞을지 모르겠더라고요.
View가 아니기 때문에 ViewModel에 넣는게 맞을까요?
아니면 ViewController의 역할이기 때문에 VC 파일에 넣는게 맞을까요??
런치와 화면 이동도 비즈니스 로직인가..?에 대한 고민이 있었습니다.
MVVM 패턴이 처음이라 아직은 어렵네요 ㅠㅠ
학습을 좀 더 하고 개선하려고 합니다.
+)
댓글을 남겨주신 고수님의 도움으로 lauch와 goHomeVC 메서드는 ViewModel로 이동하는 것이 맞다고 판단했습니다.
하지만 화면 이동을 위해 ViewController를 ViewModel에 전달하는 것은 맞지 않다고 생각을 했어요.
그래서 더 고민을 해보니 state와 version의 response 결과를 바인딩해서 처리하면 된다라는 결론을 얻었습니다!
ViewModel에서 checkState와 checkVersion을 하고, 둘의 결과가 모두 OK라면 프로퍼티를 변경해서 binding 되도록 하게끔요.
그럼 ViewController에서는 ViewModel의 프로퍼티를 바인딩해서 화면을 이동하면 되겠죠?
Launch 화면(수정 후)
버전 관리
일단 버전 정보는 서버에서 파이어베이스로 변경했습니다.
일시적인 팀이라 추후 버전이 바꼈을 때 서버 개발자에게 요청하는 게 어려울 수도 있거든요.
그래서 제가 직접 바꿀 수 있도록 파이어베이스로 변경했습니다.
버전과 함께 appleID와 bundleID도 함께 관리하려고 합니다.
원래는 do 안에 return을 넣고, catch에 기본값 구조체를 넣어줬었는데요.
그러니까 return이 두 개가 들어가서 가독성이 안 좋다고 느껴졌습니다.
그래서 snapshot을 받을 때만 do catch를 사용하고 이후는 밖의 영역에서 사용했습니다.
값을 성공적으로 가져오면 가져온 값으로 구조체를 생성해서 반환하고,
에러 상황일 때는 기본값으로 생성합니다.
옵셔널로 처리하지 않은 이유는 작은 앱이라 버전을 가져오지 못하더라도 크리티컬하지 않기 때문이에요 ㅎㅎ
좀 다른 이야기인데, 코드 컨벤션의 고민도 좀 있었습니다. (할수록 고민만 많아지는 ㅎ;;)
"가로로 긴 코드보다 세로로 긴 코드가 더 좋다"라는 문구를 봤고, 저도 이에 동의합니다.
특히 Swift는 변수명이 길어지더라도 명확한 네이밍을 권장하고 있기 때문에 가로로 무한정 길어질 수 있어요.
그래서 이를 따르기 위해서 VersionData 구조체를 생성할 때처럼 파라미터마다 개행을 넣어주려고 합니다.
다른 파일도 하나씩 수정을 하려고 해요.
ViewController
ViewController는 ViewModel 데이터바인딩과 화면 이동 코드만 남게 되었습니다.
서버와 통신하는 등의 로직은 모두 ViewModel로 옮겼어요.
댓글로 조언해주신 고수님, 다시 한 번 감사드립니다.
ViewModel의 stateData를 바인딩합니다.
stateData는 앱 운영 여부입니다. 점검 중이거나, 문제가 있으면 공지를 Alert으로 띄워줍니다.
만약 state가 ok이면 홈 화면으로 이동합니다.
여기서도 코드 컨벤션을 유지하기 위해 guard문에서 변수 하나마다 개행을 넣어줬고,
guard ... else { return }
으로 한 줄 작성이 아닌 반드시 개행을 넣는 것으로 수정하고 있습니다.
지금은 의식적으로 코드컨벤션을 유지하려고 노력하고, 이런 마인드가 익숙해지면 SwiftLint 라이브러리를 사용해보려고 합니다 ㅎㅎ
ViewModel
Http 통신에서 파이어베이스로 변경되어서 ViewModel도 수정이 많았습니다.
먼저 위에서 바인딩한 옵저버를 봅시다.
stateData의 value는 StateData입니다.
StateData 구조체는
ok, fail로 구분되는 state와 공지가 들어가는 notice를 포함하고 있습니다.
여기의 state가 ok일 때만 홈으로 이동하는 거에요.
이 두 메서드는 파이어베이스의 데이터를 불러옵니다.
마찬가지로 Swift Concurrency로 구현해서 가독성을 높였습니다.
진짜 마무리
댓글을 남겨주신 고수님 덕에 MVVM에 대해 조금 더 이해한 느낌입니다.
연쇄적으로 코드도 더 이쁘게 작성할 수 있게 되어 감사할 따름입니다.
감사합니다!
아직은 초보 개발자입니다.
더 효율적인 코드 훈수 환영합니다!
공감과 댓글 부탁드립니다.