[iOS] SwiftUI 채팅화면 MainTread 락현상 수정하기


# __[iOS] SwiftUI 채팅화면 MainTread 락현상 수정하기__

안녕하세요 __물먹고하자__ 입니다 :)
미뤄왔던 업무중에 SwiftUI쪽 채팅화면이 어느순간부터 느려지고 있어서 개선하고자 합니다.

예전에 작성했던 글 : [[SwiftUI 공부] 그룹웨어 채팅 Objc -> Swift, SiwftUI 적용기](https://xodhks0113.blogspot.com/2022/07/swiftui-objc-swift-siwftui.html)
2022년도에 변경하고, 최적화는 추가적으로 진행하지 못했었네요 ㅠㅠ

__일단 바로 시작!__

---
## 1. __원인을 찾아보자__

<!-- 이미지 수정 전 후 체크 -->
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHu1NY_-24fIAhZ8qPxYQOlaZwXSKQa_XqGpEQD4v1B6Hk1xASlwVnminYmwaIxcFvqcFk2gOqOU2e_bS-sOd6HxhRu4PQAvJCb1mBwlH63c4hQraNfsI2aDgSGmeJvdpsUiFAFNh3La4Sqf5DZQHUoZtGghWGskcyvyWuoq1EOR6zQS0H8htWbkQmddx7/s1600/%E1%84%89%E1%85%AE%E1%84%8C%E1%85%A5%E1%86%BC%E1%84%8C%E1%85%A5%E1%86%AB%E1%84%92%E1%85%AE_%E1%84%87%E1%85%B5%E1%84%80%E1%85%AD.gif" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="450" data-original-width="600" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHu1NY_-24fIAhZ8qPxYQOlaZwXSKQa_XqGpEQD4v1B6Hk1xASlwVnminYmwaIxcFvqcFk2gOqOU2e_bS-sOd6HxhRu4PQAvJCb1mBwlH63c4hQraNfsI2aDgSGmeJvdpsUiFAFNh3La4Sqf5DZQHUoZtGghWGskcyvyWuoq1EOR6zQS0H8htWbkQmddx7/s1600/%E1%84%89%E1%85%AE%E1%84%8C%E1%85%A5%E1%86%BC%E1%84%8C%E1%85%A5%E1%86%AB%E1%84%92%E1%85%AE_%E1%84%87%E1%85%B5%E1%84%80%E1%85%AD.gif"/></a></div>

> 💡수정전.gif 부분에서 스크롤시 __MainThread 락현상이 보임.__  원인을 찾아보자!

---
## 2. 말풍선에 사용되는View를 기본 Text 바꿔보자
<!-- 대화방 말풍선을 일반 Text로 바꿨을때 -->
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhunYfU41J8lD2Y7m94Qy005TBEF_z2nw066GmCWQidDE3ubHa47EsxS-p6LK4yravxO6wvBV5SD5ZDtGelr5PuYRRssB0nXQN5PVBJt-mbSVE62mP_M4TysGWRJFTMpJplViUJMtuJpTkFQcExOWa8u5ta85fWI8ime7h_n-yvpgnUOP5a18c_ynycaQIv/s1968/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202024-11-29%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%205.37.33.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="1968" data-original-width="922" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhunYfU41J8lD2Y7m94Qy005TBEF_z2nw066GmCWQidDE3ubHa47EsxS-p6LK4yravxO6wvBV5SD5ZDtGelr5PuYRRssB0nXQN5PVBJt-mbSVE62mP_M4TysGWRJFTMpJplViUJMtuJpTkFQcExOWa8u5ta85fWI8ime7h_n-yvpgnUOP5a18c_ynycaQIv/s400/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202024-11-29%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%205.37.33.png"/></a></div>

> 💡 기존에 말풍선 부분을 Text(임시)로 변경시 __MainThread 락현상이 보이지 않았음.__
 - API 통신부분에 의해 느려지는건 아님.
 - ScrollView + LazyVStack에 문제가 있는게 아님.
 - __말풍선영역에 대한 문제가 있어 보임. (여기다!!)__

---
## 3. 말풍선을 그릴때 어떤 문제인지 확인해보기

말풍선 부분을 직전 2022년도에 작업을 진행할때 View를 나눠서(정리)개발을 하였는데,
각각의 __View를 EnvironmentObject__로 사용하여 개발을 했었다.

<!-- 대화방 EnvironmentObject 사용했던 이미지 -->
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivF_89H8G_AICCNOAR_up7FiTFTGlePn550rE5aBNYJGm64U1VcLluGxfCCRUZsTtoPWlJGFZgGcH4Ch6xXltbd-R0mUJ5_5YPozUubjyHlkoiA6rrXoOzfvcpoHuKvNg3EANsjtRn3qfTrqTFFg_FTR7i9muF7uS4SwEI-Xp70JEMbyhwK3Pst8tIwef5/s1600/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202024-11-29%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%205.19.47.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="1504" data-original-width="1478" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivF_89H8G_AICCNOAR_up7FiTFTGlePn550rE5aBNYJGm64U1VcLluGxfCCRUZsTtoPWlJGFZgGcH4Ch6xXltbd-R0mUJ5_5YPozUubjyHlkoiA6rrXoOzfvcpoHuKvNg3EANsjtRn3qfTrqTFFg_FTR7i9muF7uS4SwEI-Xp70JEMbyhwK3Pst8tIwef5/s1600/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202024-11-29%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%205.19.47.png"/></a></div>

당시에는 __EnvironmentObject__ 몰라서 사용했었는데, 채팅 특성상 해당화면을 다른곳에서 쓸일이 없더라구요.
__StateObject__나 아니면 __ViewBuilder__ 함수화 해서 사용해도 상관없을 화면들로 판단되었습니다.

3개다 사용해도 문제가 없는데, 메모리 이슈라고 판단 이들어 ChatGPT 궁금증 문의!

> 💡 ChatGPT 검색후 도움건 __(EnvironmentObject 관련 검색)__
 -  Observed Updates: EnvironmentObject는 데이터가 변경될 때마다 뷰가 다시 그려지게 하는데, __여러 개의 객체가 동시에 업데이트되면 UI 스레드에서의 부하가 증가해 렌더링 속도가 느려질 수 있습니다.__
 - Dependency Chains: EnvironmentObject가 중첩되거나 서로 참조하는 경우, 그 의존 관계가 복잡해져서 일부 업데이트가 불필요하게 발생할 수 있습니다. 이로 인해 __불필요한 리렌더링이 발생하면서 성능에 영향을 줄 수 있습니다.__
 -  메모리 사용: EnvironmentObject 자체가 메모리를 크게 차지하지는 않지만, 그 안에 저장된 데이터의 크기와 해당 데이터를 __참조하는 뷰 계층의 깊이에 따라 메모리 사용이 증가할 수 있습니다.__ 특히, 상태가 여러 뷰에서 참조될 경우 시스템이 관리하는 상태의 수가 늘어나면서 메모리 관리 효율이 떨어질 수 있습니다.

__음... 제 얘기하는줄... 계층으로 값 전달하고 있었다..__
말풍선 화면을 그릴때 __ScrollView + LazyVStack 에서 itemList[item]__을
ㄴ 1) 껍데기View __environmentObject(item) 전달__
--ㄴ 2) 껍데기View에서 타입별 말풍선화면 구분 각각의 말풍선에 __environmentObject(item) 전달__
----ㄴ 3) 각각의 말풍선 중에 또 답글이거나, 다른 특수한 상황일때 __environmentObject(item) 전달__

거기에 __Published__ itemList 여서 자동갱신시점이 더 많을 수 밖에 없는 환경

추가로 EnvironmentObject을 StateObject 로 변경할껀데 대응이 맞는지 검색해보기

> 💡StateObject, ObservedObject 관련 검색!
- __StateObject__
 * 용도: 해당 객체의 소유자가 되고, 초기화와 생명주기를 관리할 때 사용합니다.
 * 상황: 최상위 뷰나 해당 객체를 처음 생성하는 뷰에 사용합니다.
 * 생명주기: @StateObject로 선언된 객체는 뷰가 다시 그려지더라도 한 번만 생성되며, 뷰가 메모리에서 사라질 때 해제됩니다.
- __ObservedObject__
 * 용도: 객체를 소유하지 않고, 이미 외부에서 소유하고 있는 객체에 의존할 때 사용합니다.
 * 상황: __상위 뷰에서 생성된 객체를 하위 뷰에서 구독__할 때 사용합니다.
 * 생명주기: @ObservedObject는 객체를 생성하거나 관리하지 않으므로, 뷰가 다시 그려질 때마다 새롭게 할당되지 않습니다. 단순히 상위 뷰로부터 전달된 객체의 변경을 반영할 뿐입니다.

그렇다면 제 상황에서는 사실 객체를 전달할 필요까지는 없어서
__EnvironmentObject__를 __ViewBuilder__로 내부 뷰 함수로 만들고,
어쩔수 없는경우에는 __ObservedObject__로 변환 하여 사용해야겠다는 판단이 드네요!

>💡 작업후... __EnvironmentObject__을 사용했을때 보다는 조금 나아진정도? 
드라마틱한 효과로 보이진 않았다 ㅠㅠ

---
## 4. 그렇다면.. 말풍선을 만드는 객체가? 문제가 있나

>💡위에까지 실험 후 말풍선을 그릴때 __Text영역(Attribute정보)__를 
그려주기 위한 설정값(Option)을 만다는 부분을 시간을 체크
- 설정값(Option)을 생성하는 행위가 __각각 0.3밀리초 만큼 걸리는 현상을 확인__

<!-- 말풍선 꾸미는 Attribute 함수 -->
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggWsPiiTzZS1G1VatLIZSxHBBGRSB12zwycuQtfRdXTWBe1ZfobCzCqgAPHm6ulKHeDFUec-7Dzrs5qy1TFv9JEvyGx1FifK1fSvLpgRZP6qIY9znWcL-nkhXIXnFY83dkh9B_suxQDP3xJqndel41Lb5YQTL5j3VzrcN4RZbYppVQKll1N_nFyipk_wds/s1600/1%29Attribute%E1%84%86%E1%85%A1%E1%86%AB%E1%84%83%E1%85%B3%E1%84%82%E1%85%B3%E1%86%AB%20%E1%84%92%E1%85%A1%E1%86%B7%E1%84%89%E1%85%AE.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="194" data-original-width="1358" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggWsPiiTzZS1G1VatLIZSxHBBGRSB12zwycuQtfRdXTWBe1ZfobCzCqgAPHm6ulKHeDFUec-7Dzrs5qy1TFv9JEvyGx1FifK1fSvLpgRZP6qIY9znWcL-nkhXIXnFY83dkh9B_suxQDP3xJqndel41Lb5YQTL5j3VzrcN4RZbYppVQKll1N_nFyipk_wds/s1600/1%29Attribute%E1%84%86%E1%85%A1%E1%86%AB%E1%84%83%E1%85%B3%E1%84%82%E1%85%B3%E1%86%AB%20%E1%84%92%E1%85%A1%E1%86%B7%E1%84%89%E1%85%AE.png"/></a></div>

각각의 말풍선마다 __멘션, Custom링크들 처리__들이 있어서 Text 표현시 __AttributeString__ 처리하고 있습니다.
위의 이미지는 해당 함수부분

문제로 생각되는 부분은 함수 내부의 __왼쪽, 오른쪽 말풍선에 따른 각각의 색상처리를 하기위한 Option객체!!__
<!-- 옵션생성부분 -->
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPxlXqsV9VGatFusOJpBztVFnYLaf_fyP5y8VNe0Qo6hjc91S0JOAK8OGiMNBjJJVo3fgXkJVGofFbiF4kSCh6Bi_P37hR0967tkxwTmKIaT99NA8PwZOxNgYB9S_x5SP0-NIJm8loJPfkFF1fuM6Y8qISlbHKrTK39UVqISOqVet3QA68HDMLaZH_prpU/s2650/2%29Attribute%E1%84%8B%E1%85%A9%E1%86%B8%E1%84%89%E1%85%A7%E1%86%AB%E1%84%80%E1%85%A1%E1%86%B9%E1%84%86%E1%85%A1%E1%86%AB%E1%84%83%E1%85%B3%E1%84%82%E1%85%B3%E1%86%AB%E1%84%87%E1%85%AE%E1%84%87%E1%85%AE%E1%86%AB.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="2218" data-original-width="2650" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPxlXqsV9VGatFusOJpBztVFnYLaf_fyP5y8VNe0Qo6hjc91S0JOAK8OGiMNBjJJVo3fgXkJVGofFbiF4kSCh6Bi_P37hR0967tkxwTmKIaT99NA8PwZOxNgYB9S_x5SP0-NIJm8loJPfkFF1fuM6Y8qISlbHKrTK39UVqISOqVet3QA68HDMLaZH_prpU/s600/2%29Attribute%E1%84%8B%E1%85%A9%E1%86%B8%E1%84%89%E1%85%A7%E1%86%AB%E1%84%80%E1%85%A1%E1%86%B9%E1%84%86%E1%85%A1%E1%86%AB%E1%84%83%E1%85%B3%E1%84%82%E1%85%B3%E1%86%AB%E1%84%87%E1%85%AE%E1%84%87%E1%85%AE%E1%86%AB.png" width="600"/></a></div>

생각해보면 저 __Option객체가 왼쪽1개, 오른쪽1개__ 거의 고정이였는데, 왜 텍스트 값 뽑을때마다 생성했는지 모르겠내요 ㅠㅠ (실수)
[[Swift 공부] 구조체 vs 클래스 (Struct vs Class)](https://xodhks0113.blogspot.com/2019/09/swift-vs-struct-vs-class.html) 예전에 객체 생성실험도 했었네요

기능상으론 동일하지만, __생성부분을 1번만 전역으로 참조__해서 쓰는형식으로 수정결정!
<!-- 개선전 속도체크 -->
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtp3ZTP2xJ06RqCgODfvILSdrG0VdUSZf_xlf5pHkE2qPLIw8umSJO-6bp39VhX-XJoIiihcu0sSHGVium062RTHUxKp0nUNYL7sW1FlWBJqSjsr3Phkcp689Pp_-61lVZ3j6qIrN56pBWL1HG3EB8dvH5czCMP7nFh9LaE8UycFXbQ-rsddXPFD8DtE-m/s1600/3%29%E1%84%80%E1%85%A2%E1%84%89%E1%85%A5%E1%86%AB%E1%84%8C%E1%85%A5%E1%86%AB%20%E1%84%85%E1%85%A9%E1%84%80%E1%85%B3.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="466" data-original-width="2766" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtp3ZTP2xJ06RqCgODfvILSdrG0VdUSZf_xlf5pHkE2qPLIw8umSJO-6bp39VhX-XJoIiihcu0sSHGVium062RTHUxKp0nUNYL7sW1FlWBJqSjsr3Phkcp689Pp_-61lVZ3j6qIrN56pBWL1HG3EB8dvH5czCMP7nFh9LaE8UycFXbQ-rsddXPFD8DtE-m/s1600/3%29%E1%84%80%E1%85%A2%E1%84%89%E1%85%A5%E1%86%AB%E1%84%8C%E1%85%A5%E1%86%AB%20%E1%84%85%E1%85%A9%E1%84%80%E1%85%B3.png"/></a></div>

수정 전 속도체크하면 __빨간네모참고__ Option 객체 생성시마다 0.021초씩 누적되는 현상 확인

<!-- 개선후 속도체크 -->
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg8zpJF7PhbAjGXwfjAuj3vJRtuVbm_1eRJk9o15sAV-tan2wgeOF2TIDqf5v71hrP3a-ue7ZIaTJdt86d56Mqam0apk3xD4RhJhfKbfMLufJ_SGcxRu9_d7IKwX00dk6y8JJcIZd9udN_oJbD9tlKjdjhMCuZF11evrmidY8RkQ-VGnassIrdOpIgJvtt2/s1600/4%29%E1%84%80%E1%85%A2%E1%84%89%E1%85%A5%E1%86%AB%E1%84%92%E1%85%AE%20%E1%84%85%E1%85%A9%E1%84%80%E1%85%B3.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="288" data-original-width="2748" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg8zpJF7PhbAjGXwfjAuj3vJRtuVbm_1eRJk9o15sAV-tan2wgeOF2TIDqf5v71hrP3a-ue7ZIaTJdt86d56Mqam0apk3xD4RhJhfKbfMLufJ_SGcxRu9_d7IKwX00dk6y8JJcIZd9udN_oJbD9tlKjdjhMCuZF11evrmidY8RkQ-VGnassIrdOpIgJvtt2/s1600/4%29%E1%84%80%E1%85%A2%E1%84%89%E1%85%A5%E1%86%AB%E1%84%92%E1%85%AE%20%E1%84%85%E1%85%A9%E1%84%80%E1%85%B3.png"/></a></div>

수정 후 호출되는 위치는 같지만, 생성을하지 않다보니 __여러번 호출되도 시간초가 차이가 나지 않음.__

---
## __마무리__
앞서 SwiftUI로 채팅부분을 만들때 SwiftUI를 처음 도입하기도 했고, 지식을 많이 얻지 못한상태에서 개발했었는데 나쁘지 않았던 결과였습니다.
하지만 역시나 딱히 __코드개선없이 기능만 추가되다보니 어느순간 관리가 제대로 되지 않고 있었네요.__

그래도 뒤늦게 나마 사용성 문제가 되는 부분을 고쳐서 다행이라고 생각하네요.

추가로 개선을 하면서 __ScrollView + LazyVStack의 이슈건 Offset 이동이 불가능한 부분__으로 인하여(자연스러운 스크롤 고정효과가 안됨)
최근에 __iOS16 이상 UICollectionView + SwiftUI 버전__으로도 추가 개선중입니다. 이부분은 작업 완료되면 공유드리겠습니다.


오늘은 이만!

즐거운 코딩되세요~

끝.


댓글