[SwiftUI 공부] 그룹웨어 채팅 Objc -> Swift, SiwftUI 적용기
안녕하세요. 물먹고하자 입니다 :)
올해 1월에 프로젝트에 사용할 특수한 View덕에 SwiftUI 공부를 시작하였는데,
진짜.. 급발진해서 그룹웨어 내의 핵심모듈중 하나를 SwiftUI로 변경했습니다.
(시작하면 멈출 수 없어...!!)
중간에 정리를 하면서 했으면 좋겠지만, 문제는 ㅠㅠ UIKit(Objc, Swift)는 빠삭하지만,
SwiftUI의 Combine, 라이프 사이클, 기존작동건에 대한 지원여부 등등
100% 이해를 하지 않은 상태에서 시작을 하여 정신줄 놓아지는걸 잡아가면서 진행을 하였습니다.
(결과론적으론 98%정도는 성공 ?)
오늘은 코드보다 전환시에 막혔던점 고려해야할 점 등을 공유드립니다!
대화방 (복합적인 말풍선 + 이미지 등등) |
변경전 문제점 파악
[문제로 되고있다고 생각하는 점]
- CollectionView FlowLayout 사용중 (iPad Split 지원시 재계산이 안되고 있는 이슈)
- FlowLayout Custom 라이브러리 사용중 이미 지원끊긴 라이브러리 사용 (대체 필요)
- 표현방식 별 Cell x 2[왼쪽, 오른쪽] 총 26개 + 각각의 Xib (하....)
- 답글기능이 들어가면서 기존 Cell x 2배가 더 늘게 생김 ...
- 앞으로의 유지보수 및 신규기능을 덧붙일 자신감 하락(?)
[개선하고자 하는점]
- 아키텍쳐 변환 MVC -> MVVM
- 공통화 영역에 대한 정리
- 불필요 라이브러리 제거
- 표현방식 별 Cell의 유동적인 StackView 도입
- 데이터 처리 및 정리모듈 개선
기존에 있는 Class.jpg ... [.m, .h, .xib] |
사전실험
시작에 앞서 SwiftUI 나오기전에 개선 시도를 안한 건 아니다.
참고될만한 내용이라 공유드리고 시작합니다.
"표현방식 별 Cell의 유동적인 StackView 도입" 을 먼저 검증 하였는데, 이부분만 기존 화면에서 전환을 해도 Cell의 갯수를 줄일수 있어서 왼쪽, 오른쪽으로 한벌씩만 만들어도 대만족(?) 이라고 생각했다.
왼쪽 Cell에 표현될 정보.zip |
이전에 iOS 9.0이 발표하고 새로나온 UIStackView가 나오자마자 검증시 저놈의 Cell들을 합쳐서 사용할 수 없을까 검증을 한적이 있었다. (UIKit 기준)
이론은 매우 심플하고 좋았다.
순서대로 이모티콘,이미지 < 동영상 < +말풍선 < URL프리뷰 < 화상대화 < 사다리게임 등등등
이모든것을 합친 VStackView에 넣어놓고, Type에 따라 숨김처리를 하면 되겠구나!! 했는데
(결론적으로 완벽하지 않았다.... )
특히 Cell 개념상 재사용을 목적으로 둔 UIView이기 때문에
직전에 "이모티콘,이미지" 를 사용하고, 재사용시에는 "화상대화" 를 쓸 경우 Hidden 시키는 애니메이션이 보여 (UIView layoutIfNeeded 추가 사용시) 잔상이남게 되었고, 간혈적으로는 Hidden은 시켰지만, View 안에 SubView들이 zero 사이즈인데, 그대로 보이는 현상까지 나타났었다.
그리고 저 많은 View를 미리 생성해놓고 숨겨놓고 사용하는거기 때문에 사용하지 않아도 일단 메모리에 할당이 되고 시작하는것도 문제로 삼았다.
([참고] 테스트 시기는 iOS 9.0 발표이후 였습니다.)
앞서 실패 경험의 미련을 버리지 못하고, 이번에는 Cell에 SwiftUI를 HostViewController로 생성해서 그리는 방식을 채택해 보면 어떨까? 를 구상해보았다.
결론은.. 메모리 할당 및 Cell을 꾸며줄때 결국 HostViewController에 rootView를 다시 생성해서 넣어주는 방식이라 실제 메인쓰레드에서 약간에 렉이 걸리는 현상이 보이기도 했다. (효율적인 문제 판단)
#. 이번에 WWDC 에서 iOS16 이상 Cell 에서 직접 SwiftUI를 구성하는게 진작에 나왔었으면... 이부분에서 최대한 어떻게든 했었지 않았을까? 생각하고 있다.
검증 결론
결국에 껍데기는 UIViewController로 시작하고, 안에 있는 채팅영역(스크롤포함) 영역 SwiftUI로 만드는 형식으로 구성하게 되었다.
이유는 기존에 사용하고 있는 UIKit 기반의 공통UI의 사용 부분을 그대로 이용하면서 혹시(?) SwiftUI로 대처가 안될시 UIKit 으로 급히 처리하려는 목적이 컸다.
SwiftUI 적용영역 |
Objc(UIKit) -> Swift(SwiftUI) 전환 시작
처음부터는 진행시 참... 막막했다.
SwiftUI를 딱 간단하게 정리하자만 UIKit의 복잡성을 직관적으로 사용, 그린다! 인데, UIKit에서 제공하는 모든 기능을 제공해주진 않는다. (심지어 iOS15 이상 지원도 꽤 많음)
그래서 개발당시에 "어? 이 함수가 없다고? 어? 이게 안돼? 어....? 이것도" 를 제일 많이했던것 같다.
1. MVC -> MVVM 아키텍쳐 전환
기존 Objc 코드가 MVC 였다..(스파게티소스가 난무) 네트워크 통신모듈만 간신히 떼어놓은 상태
Controller의 역할이 커지다 보니 난잡하기도 하고, SwiftUI로 전환하면서 아키텍쳐도 MVVM으로 전환하였다.
기존의 SwiftUI 가 UIKit 기반 코드가 짧게 보이는데, 결국 ViewModel에 코드가 들어가서 라인수는 비슷비슷한 느낌이다.
Objc ViewModel (모델객체가 있는것에 감사함... ㅠㅠ) |
SwiftUI 전환을 위한 ViewModel : ObservableObject 생성 (아래 중략...) |
고민이 들게하는 ViewModel Line수 Swift CleanVIP에서 네트워크 후 데이터 반영을 라우터를 통해 대입하던데.. 개선에 여지가 보임.. 하 ... ^^ |
2. View 영역 SwiftUI 개발
기존 UIKit은 Objc + Xib 기반이였다. 이부분을 SwiftUI로 개발하려니 재사용할 수 있는게 없었다.
리페토링겸 기존소스를 운영중으로 냅둔 상태에서 모든걸 새로운 Class로 시작하였는데, 코드의 개발의 난이도 보다는 코드 정리(재활용 및 유지보수)를 주된 목적으로 개발을 하였다.
SwiftUI 개발시 표현되는 순서 열심히 주석달아놓기 |
SwiftUI 쪽으로 개발했을시 View를 최대한 나눠서(정리) 개발을 진행하였는데,
각각의 사용될 View를 EnvironmentObject로 데이터를 공유해서 구분지음 |
각각의 표현될 View 구성 |
초반에는 "EnvironmentObject" 을 활용한 단독 View로 구성하였다.(이거 외에는 구분짓는방법을 몰랐다가 맞을것 같다..)
대화방 특성상 다른모듈과 공통으로 쓸일이 없다보니 개발 중반부터는 "@ViewBuilder"를 통해서 정리를 하였다.
각각 표현될 View의 조합 시에는 ViewBuilder 활용 |
3. UIKit, SwiftUI 계층구조 차이점
UIKit <----> SwiftUI View hierarchy (계층보기) |
이게 아마 UIKit 과 SwiftUI의 가장 큰 차이점이라고 생각을 하는데,
UIKit 기준으로 보면 Cell > Cell 내 Item 별 구분이 되어있어서 어느 뷰에 할당되어 있는지가 눈에 띈다.
SwiftUI의 경우는 이미그려진 부분은 계층구분없이 표현되고 있다.
처음에는 이게 버그(?) 라고도 생각을 했었는데, (나는 그릴때 구분을 지어놨으니깐)
UIKit은 메모리에 할당된 View를 쌓아서 보여주는 구조
SwiftUI은 캔버스에 View를 그려서 보여주는 구조
이게 가장 큰 차이점인것 같다.
4. SwiftUI 개발중 맛본 한계
한번더 언급하자면, 필자는 UIKit (iOS 4.0) 때부터 개발을 해왔기 때문에 당연하게(?) 생각되던게 SwiftUI에서는 안되는게 많았다.
그 중 당황한 몇 가지를 설명하자면,
4-1. ScrollView + LazyVStack : ContentSize
몇몇 제약사항으로 인해 List를 사용하지 않고, ScrollView + LazyVStack 을 사용하였는데, (실제 메모리 활용은 TableView, CollectionView Cell들과 비슷하게 작동함)
ContentSize를 기본으로 가지고 오는 함수가 없다.
별도로 NameSpace, PreferenceKey를 활용해서 어찌어찌 구해올 수는 있지만 (이것도 편법)
당연히 알 수 있을정보라고 생각했는데, 못구해 온다는게 충격적 이였음.
4-2. onLongPressGesture 롱클릭이벤트 중 현재누르고 있는 Point 찾기
솔직히 해결을 못해서 결국 UI의 일부를 UIKit과 조금 다르게 간케이스인데,
롱클릭시 현재 View의 누르고 있는 Point를 제공하지 않는다.
UIGestureRecognizer 에서는 Point 정보를 가져와 그 위치에 화면을 띄우는 액션이 있었는데,
SwiftUI에서는 제공되지 않아 해당 롱클릭의 껍데기 View에 대략적으로 위치하게 변경하였다.
당연히 저와 같은 케이스가 많아서 찾아보니
Drag(위치값 알수있음) + Tap + LongTap을 합쳐서 위치값을 가지고 저장 후 롱클릭 이런식으로 사용할 수 있다.
문제는 ScrollView가 먹통이 된다는거 ^^.... (그래서 포기)
4-3. ObservableObject > @Published 무한반복이 나오는 현상
SwiftUI에 가장큰 장점인 VM에 값을 변경하면 @Published를 통한 View의 자동갱신인데,
이번에는 + 라이브러리를 사용하다가 발생한 건이다.
SDWebImage의 SwiftUI버전 "AnimationImage" or "WebImage" 두가지를 제공하는데,
WebImage는 기본 제공이며, AnimationImage는 기본 + 애니메이션 특화인 뷰 여서 추가적인 내용은 덜본상태로 AnimationImage 잘 작동하였다.
문제의 발생은 서버의 문제가 생겼을 때였는데,
SwiftUI View 그리기시작 > 이미지View영역 (AnimationImage 사용) > Http 이미지요청 > 실패 > 실패화면 갱신 @Published >
SwiftUI View 그리기시작 > 이미지View영역 (AnimationImage 사용) > Http 이미지요청 > 실패 > 실패화면 갱신 @Published > ..... (무한반복으로 인한 MainThread lock 현상)
이 현상을 찾아보니 ㅠㅠ 이미 진작부터 고치지 못하는 현상도 있었다. 물론 SDWebImage가 Objc 때부터 유명했던 라이브러리라 WebImage로 사용시에는 전혀 문제가 없었는데, 추가된 AnimationImage 에서는 저런 경우도 발생하였었다.
(만약에.. 모르고 배포됐으면, 아찔할뻔했다.)
4-4. KeyValueCode UIView 해킹
앞서 98% 완성중 2%에 해당하는건데, UIKit에서 ScrollBar 영역을 구해서 옆에 날짜를 붙여주는 UI가 있었다.
ScrollView.subView.last 는 ScrollView Bar |
ScrollViewBar의 Fame값과 Alpha 값을 가지고 와서 옆에 날짜를 표현해주는 코드인데......
당연하달까 SwiftUI에서 다른방법을 써도 접근을 못했다.
편법으로 접근이 가능한데, SwiftUI ScrollView를 -> UIScrollView UIViewRepresentable 를 통해 SwiftUI에서 사용하면 되는데, 그럴꺼면... 애초에 SwiftUI로 전환을 안하는게 낫다고 생각했다.
해당 기능은 사용하는데는 지장을 주지 않는 화면이라 약간 무시하고 넘어간 경우긴하다. (안들켰다 ㅎㅎ)
마무리
블로그 글도 몇일째 쓰고 있지만, 실제로 UIKit(Objc) -> SwiftUI 로 변경+기능추가 하는데, 2달 가까이 걸린것 같다. 이것만 집중해서 했으면 빨랐을 수도 있지만 정확하게 모르는상태가 맞으니... 아마 빠른거겠지?
SwiftUI가 iOS 13.x 때 적용이 가능하지만, 최소 iOS 14.x 은 되야 그나마 UIKit -> SwiftUI로 전환정도는 가능한 것 같다.
이번에 하면서 느낀점은 Objc -> Swift로 전환 되었듯이 UIKit -> SwiftUI로 앞으로 빠르면 1년안에 추세가 바뀔 것 같다. (심지에 Apple 제공 기본라이브러리도 SwiftUI가 기본으로 나오고 있음)
애니메이션처리 간단한 View의 구성정도는 SwiftUI가 편하고 강력하지만, 무리해서 모든걸 바꾸진 않아도 될것 같다.
현재 하고있는 프로젝트에서도 앞으로의 신규개발/유지보수를 위한 부분들 먼저 검증후에 천천히 바꾸고 있으며, 향후 1~2년 정도 후에는 UIKit 영역이 SwiftUI로 많이 대처 될 것 같다.
오늘 UIKit -> SwiftUI 전환기 두서없이 작성하였는데 읽어주셔서 감사합니다.
즐거운 코딩 되세요~ :)
끝.
ㅋㅋㅋㅋㅋㅋ 와 히스토리를 여기서 보는데 정말 재미있네요..! iOS 12를 기준으로 시작한 저는 그저 신기할 따름입니다 ㅋㅋ
답글삭제