업데이트
+ 24.06.20 일자로 올리브영 공식 앱에 해당 문제를 개선한 업데이트가 반영됨


0. 개요
평소처럼 올리브영 앱을 사용하다가 문득 불편함을 느꼈다.
올리브영 앱의 콘텐츠 부분은 웹뷰로 제작되어, 뷰를 이동할 때마다 새로운 페이지를 로드하는 방식으로 동작한다.
이 과정에서 선택했던 옵션 혹은 탐색 중이던 스크롤 위치를 잃기도 했다.
UINavigationController가 떠올랐다.
stack 구조의 계층적 네비게이션을 제공하면, 그만큼 데이터 요청이 줄어들지 않을까? 사용성도 좋아지지 않을까?
안 그래도 많은 이미지를 보여주는 H&B 서비스인데, 비용도 줄어들지 않을까?
그래서 만들어보기로 했다.
1. 준비
1.1. 기술 스택 파악
| 카테고리 | 기술스택 | 나의 경험 | 참고 자료 |
| 언어 | Swift, Objective-C | O, X | |
| 라이브러리(UI) | UIKit | X | |
| 라이브러리 | Cocoa Touch | X | https://babbab2.tistory.com/51 |
| 개발 도구 | Xcode | O | |
| 동시성 | RxSwift, ReactorKit | X | https://oliveyoung.tech/blog/2023-05-20/OliveYoung-iOS-ReactorKit/, |
| 뷰 방식 | WKWebView | X | https://hilily.tistory.com/78, https://velog.io/@gnwjd309/iOS-WKWebView, https://thoonk.tistory.com/87 |
| 구조 | MVVM, MVVM-C | O | |
| 데이터베이스 | Core Data(?) | O | |
| 품질 관리 도구 | SwiftLint | O |
지피지기면 백전불태이다.
구현에 앞서 올리브영 앱에서 사용 중인 기술 스택을 파악하기로 했다.
공식 홈페이지, 공식 유튜브, 테크 블로그, 신입 채용 공고, 경력자 채용 공고, 링크드인에서 자료를 수집했다.
이번 프로젝트에서 가장 중요한 웹뷰는 `WKWebView`를 사용하여 구현한 것으로 추정된다. (링크드인 경력 채용 참고)
1.2. 웹뷰 구현 방식

iOS에서 웹뷰 구현 방식에는 3가지 방법이 있다.
- UIWebView
- WKWebView
- SFSafariViewController
UIWebView
iOS 2.0에 도입되었다.
iOS 8.0 이하 버전을 지원이 가능하다.
그러나 deprecated 되었다.
WKWebView
iOS 8.0에 도입되었다.
WebKit 프레임워크의 일부이다.
웹 페이지에서 할당하는 메모리는 앱 메모리와 별도의 스레드에서 관리한다는 특징이 있다.
메모리 문제에 대해 앱 안정성이 높아진다.
UI 커스터마이징이 비교적 자유롭다.
SFSafariViewController
iOS 9.0에 도입되었다.
내장 Safari 브라우저와 같은 사용자 경험을 제공한다.
UI 커스터마이징이 제한적이다.
1.3. 웹 ↔ 앱 통신 방식
웹은 js로 작성된 로직을 바탕으로 HTTP에 따른 요청과 응답을 서버와 주고 받는다.
iOS 앱은 (MVVM 구조의 경우) Swift로 작성된 로직을 바탕으로 ViewModel이 Model과 View 사이를 중개하며 데이터를 주고 받는다.
그렇다면 웹과 앱 사이는 어떻게 통신할까?
더군다나 현재로써는 올리브영의 웹과 앱 내부 코드에 접근할 수 없는 상황이다.
이번 프로젝트 진행을 위해 핵심적인 부분이다.
1.3.1. 웹 → 네이티브 앱
`WKScriptMessageHandler` 프로토콜을 사용하면 JavaScript에서 네이티브 앱으로 메시지를 보낼 수 있다.
이 프로토콜을 채택한 객체를 WKWebView의 `WKUserContentController`에 등록해야 한다.
// iOS 네이티브 앱
import WebKit
class ViewController: UIViewController, WKScriptMessageHandler {
var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
let contentController = WKUserContentController()
contentController.add(self, name: "jsHandler")
let config = WKWebViewConfiguration()
config.userContentController = contentController
webView = WKWebView(frame: self.view.frame, configuration: config)
self.view.addSubview(webView)
if let url = URL(string: "https://example.com") {
webView.load(URLRequest(url: url))
}
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == "jsHandler", let messageBody = message.body as? String {
print("웹으로부터 받은 메시지: \(messageBody)")
// 메시지 처리
}
}
}
설정 방법은 아래와 같다.
- WKUserContentController 생성
- WKScriptMessageHandler 프로토콜 구현
- WKWebViewConfiguration에 WKUserContentController 설정
- WKWebView 초기화
// 웹 자바스크립트
<body>
<h1>웹에서 네이티브 앱으로</h1>
<button onclick="sendMessageToNative()">Send Message</button>
<script>
function sendMessageToNative() {
window.webkit.messageHandlers.jsHandler.postMessage("웹에서 보내는 메시지");
}
</script>
</body>
웹에서는 `webkit.messageHandlers.설정name.메서드`을 통해서 네이티브 앱으로 메시지를 보낼 수 있다.
1.3.2. 네이티브 앱 → 웹
WKWebView의 `evaluateJavaScript` 메서드를 사용하면 네이티브 코드에서 JavaScript 코드를 실행할 수 있다.
`evaluateJavaScript`는 클로저를 통해 실행 결과나 에러를 반환하므로, 적절한 처리가 필요하다.
// iOS 네이티브 앱
let clickButtonScript = "document.getElementById('myButton').click();"
webView.evaluateJavaScript(clickButtonScript) { (result, error) in
if let error = error {
print("에러: \(error)")
} else {
print("버튼 클릭 성공")
}
}
위와 같이 자바스크립트 내 특정 버튼을 클릭하는 것이 가능하다.
// iOS 네이티브 앱
let changeColorScript = "document.body.style.backgroundColor = 'lightblue';"
webView.evaluateJavaScript(changeColorScript) { (result, error) in
if let error = error {
print("에러: \(error)")
} else {
print("배경색 변경 성공")
}
}
위와 같이 DOM을 조작하는 것도 가능하다.
지금까지 웹과 앱의 통신 방식을 알아봤다.
이제 통신을 통해 얻은 데이터를, 어떻게 stack 구조의 계층적 네비게이션에 담아낼까 고민해봐야 한다.
오늘의 주인공인 `UINavigationController`와 `WKWebView의 네비게이션 제어 방법`에 대해서 알아보자.
1.4. UINavigationController
1.4.1. 정의

공식 문서에 따르면 UINavigationController는 stack 구조의 계층적 네비게이션을 정의하는 뷰 컨트롤러 컨테이너이다.
1.4.2. 동작 방식

위 그림은 동작 방식을 표현한 이미지이다.
대부분의 iOS 네이티브 앱에 적용된 네비게이션 방식이다.
네비게이션을 통해 탐색을 할 때, 현재 View 위에 다음 View가 쌓이는 방식으로 동작한다.
따라서 `뒤로 가기 == 쌓여있는 View 제거하기`이므로, 이때 새롭게 페이지를 로드할 필요가 없다.
1.4.3. 요소

- viewControllers
- viewControllers는 여러 개의 viewController를 관리할 수 있는 container viewController이다. viewControllers의 타입은 [viewController]라고도 할 수 있겠다. 해당 배열은 push, pop 메서드로 관리된다.
- navigationBar
- 타이틀, 뒤로가기, 설정 등 UI 요소들이 배치되는 상단 바이다.
- toolbar
- 공유하기 등 UI 요소들이 배치되는 하단 바이다. 기본적으로 UINavigationController에서는 숨김 처리되어 있다.
- delegate
- 특정 Event에서 사용할 수 있는 delegate가 선언되어 있다. 특정 viewController를 보이게 하거나, 이동 간 애니메이션을 설정하기 위해 사용한다.
1.5. WKWebView의 네비게이션 제어 방법
1.5.0. 발견
1.3.에서 웹과 앱의 통신 방식에 대해서 알아보았지만, 한계가 존재한다.
올리브영의 내부 코드를 모르는 입장에서, 원하는 요소를 정확하게 타겟하여 통신하는 것이 쉽지 않았다.
다행히 WKWebView는 웹 페이지의 로드 및 네비게이션을 제어하기 위해 `WKNavigationDelegate` 프로토콜을 제공한다.
이 프로토콜은 웹 뷰에서 일어나는 다양한 네비게이션 이벤트에 대한 콜백 메서드를 정의한다.
내부 코드를 모르더라도, 네비게이션 이벤트가 발생할 때마다 UINavigationController에 viewController를 추가 혹은 제거하는 것이 가능하다.
1.5.1. 종류
1. 네비게이션 정책 결정 `webView(_:decidePolicyFor:decisionHandler:):`
// iOS 네이티브 앱
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if let url = navigationAction.request.url {
print("URL 요청 시도: \(url)")
// 특정 URL에 대해 로드를 취소하거나 허용할 수 있습니다.
if url.absoluteString.contains("restricted") {
decisionHandler(.cancel) // 요청 취소
return
}
}
decisionHandler(.allow) // 요청 진행
}
이 메서드는 웹뷰가 새로운 URL을 로드하려고 할 때 호출된다.
여기서 로드할지 말지를 결정할 수 있다.
이번 프로젝트에서 가장 핵심적인 메서드이다.
2. 네비게이션 시작 시 호출 `webView(_:didStartProvisionalNavigation:):`
// iOS 네이티브 앱
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
print("로딩 시작: \(webView.url?.absoluteString ?? "")")
}
웹뷰가 콘텐츠 로드를 시작했을 때 호출된다.
3. 서버 리디렉션 응답을 받았을 때 호출 `webView(_:didReceiveServerRedirectForProvisionalNavigation:):`
// iOS 네이티브 앱
func webView(_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!) {
print("리디렉션 응답 받음")
}
서버에서 리디렉션 응답을 받았을 때 호출된다.
4. 네비게이션 완료 시 호출 `webView(_:didFinish:):`
// iOS 네이티브 앱
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("로딩 완료: \(webView.url?.absoluteString ?? "")")
}
웹뷰가 컨텐츠 로드를 완료했을 때 호출된다.
이번 프로젝트에서는 사용자의 이벤트로 발생하는 네비게이션 요청 URL을 `webView(_:decidePolicyFor:decisionHandler:):`로 처리하여 UINavigationController에 추가하거나, 새롭게 로드하는 방식으로 구현했다.
1.6. 분석
1.6.1. 분석 도구


분석 도구로는 Xcode 내장 성능 분석 도구인 `Instruments`와 HTTP 네트워크 트래픽 분석 도구인 `Charles Proxy`를 이용했다.
Instruments의 기본 기능인 `HTTP Traffic`을 사용하지 않은 이유는, WKWebView에서 발생하는 HTTP 정보를 확인할 수 없었기 때문이다. (애플 개발자 포럼 공식 답변: https://forums.developer.apple.com/forums/thread/699936)
Instruments 참고: https://developer.apple.com/videos/play/wwdc2019/411/
Charles Proxy 설정: https://techblog.gccompany.co.kr/charles-proxy-소개-4c4a3bbc8994
1.6.2. 분석 항목
분석 목적은 앱 사용성 개선 정도의 측정으로, `CPU 사용량`, `메모리 사용량`, `네트워크 사용량`을 기준으로 했다.
- CPU 사용량: Instruments의 `Time Profiler`
- 메모리 사용량: Instruments의 `Allocations`
- 네트워크 사용량: Charles Proxy의 `Requests`, `Responses`, `Combined`
1.6.3. 분석 플로우
다양한 시나리오를 검증할 수 있는 3가지 케이스를 선정했다.
내부 JS 동작이 아닌 URL 중심으로 화면의 전환이 발생하며, 사용자의 일반적인 시나리오를 기준으로 했다.
- 메인 홈Main (기존 앱의 홈 탭에서 선택 정보가 사라지는 것에 주목)
- `카테고리 랭킹` → `클렌징` 탭 선택
- 해당 탭에서 `1~5위` 상품 조회 → 뒤로 가기 → 조회 → ... (반복)
- `메인 홈`으로
- 검색Search (기존 앱의 검색 페이지에서 선택 정보가 사라지지 않는 것에 주목)
- `검색 페이지`로 이동
- `틴트` 검색
- 해당 페이지에서 `1~4위` 상품 조회 → 뒤로 가기 → 조회 → ... (반복)
- `검색 페이지`로
- 셔터Shutter (계층적 네비게이션에 View를 여러 개 쌓았을 때 메모리 사용량을 측정하기 위함)
- `카테고리 랭킹` → `클렌징` 탭 선택
- `1위` 상품 `조회`
- `리뷰` 탭 선택
- `이 상품을 태그한 셔터 게시물` → `첫 번째 셔터` 조회
- 해당 셔터에서 사용한 `첫 번째 상품` 조회
- `리뷰` 탭 선택
- `이 상품을 태그한 셔터 게시물` → `첫 번째 셔터` 조회
- 뒤로 가기 → 뒤로 가기 → 뒤로 가기 → `메인 홈`으로
1.6.4. 분석 방법
최초에는 Xcode의 `UITests`을 사용하고자 했다.
그러나 WKWebView로 모바일 올리브영몰 도메인(https://m.oliveyoung.co.kr/m/mtn?menu=home)을 띄울 경우, 웹 요소의 Identifier가 None으로 설정되어 있어 어려움을 겪었다.
또한 Xcode의 Record 기능 또한 WKWebView 환경에서 정상적으로 기록되지 않는 문제가 있었다.
그리하여 정해진 플로우에 맞춰 수동으로 테스트를 진행했다.
대신 오차가 발생할 수 있으므로 전체 플로우 수행에 소요되는 시간은 분석 항목에서 제외했다.
2. 구현
2.1. 앱 아키텍처

- BaseWebViewController (기본 클래스)
- GenericWebViewController와 ViewController의 공통 기능을 정의한다.
- 뷰를 초기화하고 설정하며, 웹 페이지를 로드하는 기본 메서드들을 포함한다.
- 네비게이션을 제어하고, 특정 URL을 차단하거나 새로 고침할지를 결정한다.
- 제어: `WKNavigationAction`을 감지하면 로직에 따라 URL을 차단하거나, 새로 고침하거나, stack에 추가한다.
- 차단: 모든 URL을 받아서 처리하기 때문에 불필요한 요청을 차단한다. (예: `about:blank`, `gum.criteo.com/`, ...)
- 새로 고침: 하단 탭바, 검색창, 장바구니로 이동하는 경우 새롭게 페이지를 로드한다.
- ViewController (초기 로드용 서브클래스)
- BaseWebViewController를 상속받아 앱이 처음 실행되었을 때 로드되는 초기 뷰 컨트롤러.
- viewDidLoad 메서드에서 초기 URL을 로드한다.
- GenericWebViewController (특정 URL 로드용 서브클래스)
- BaseWebViewController를 상속받아 특정 URL을 로드하는 뷰 컨트롤러.
- 다른 뷰 컨트롤러(ViewController)에서 새로운 URL을 로드하기 위해 계층적 네비게이션에 추가될 때 사용한다.
2.2. 어려웠던 문제
2.2.1. 네비게이션 바
현재 올리브영 앱은 콘텐츠 부분은 WKWebView로, 상단 네비게이션 바와 하단 탭 바는 네이티브로 구현되어 있다.
따라서 UINavigationController를 적용했을 때 기존 네비게이션 바는 동작하지 않았다.
고민하다가 기존 네비게이션 바 위에 UINavigationController 전용 네비게이션 바를 추가했다.
미관을 다소 해쳤지만 정상 동작했다.
2.2.2. 무한 루프
이번에 알게 된 사실이다.
모바일 올리브영몰에서 URL 요청을 보내면, 무수한 리디렉팅 요청과 내부 삽입된 URL 요청이 쏟아진다.
실제로 시뮬레이터를 실행시키고 잠시 화장실에 다녀왔는데, 모바일 올리브영몰로부터 접속 차단을 당한 경험이 있다.
UINavigationController의 stack에 뷰컨트롤러가 300개 가까이 쌓여있었다.
`decidePolicyFor` 메서드로 네비게이션 정책을 정하는 과정에서 `lastLoadedURL` 변수를 설정하여, 동일한 URL을 반복하여 요청받는 경우 해당 요청을 취소할 수 있도록 개선했다.
2.2.3. JS 내부적으로 호출하는 요청
JS는 사용자가 요청한 URL 뿐만 아니라, 특정 이벤트를 트리거로 내부적으로 실행되는 요청이 많았다.
예컨대 라이브 탭에 접속하면 비디오가 자동 실행된다.
이 요청을 처리하려면, 웹 ↔ 앱 통신이 이루어져야 한다.
그러나 해당 프로젝트는 URL 요청만을 다루고 있었기에, 이러한 문제가 발생하는 경우는 분석 플로우에서 제외했다.
3. 데이터 분석 결과
3.0. 선요약
(모두 iPhone 14 모델 실제 기기에 빌드하여 테스트했다.)
(1.6.3.에서 정의한 3가지 분석 플로우을 기준으로 했다.)
(이전 버전에 비교했을 때 개선 버전을 기준으로 작성했다.)
- CPU 사용량: 대체로 비슷한 양상을 보였다.
- 메모리 사용량: 1번과 2번 플로우에서는 효율적이었다. 다만 3번 플로우에서는 메모리 사용량이 2배 많았다.
- 네트워크 사용량: 대단히 효율적이었다. `Requests`는 64.7%, `Responses`는 20.6%, `Combined(종합)`는 33.5% 적은 사용량을 보였다.
3.1. 데이터
3.1.1. Charles Proxy (네트워크 사용량)
► 주요 항목 설명
네트워크 속도에 영향을 끼치는 항목들
- DNS Time (DNS 시간):
- DNS 조회에 소요되는 시간.
- 네트워크 속도에 직접적인 영향을 미치며, DNS 시간이 길어지면 웹 페이지 로딩 시간이 길어질 수 있음.
- Connect Time (연결 시간):
- 서버와의 연결 설정에 소요되는 시간.
- 연결 시간이 길어지면, 초기 연결 설정에 시간이 많이 걸려 전체 응답 속도가 느려질 수 있음.
- TLS Handshake Time (TLS 핸드셰이크 시간):
- TLS 핸드셰이크를 완료하는 데 소요되는 시간.
- 보안 연결을 설정하는 과정이기 때문에, 핸드셰이크 시간이 길어지면 데이터 전송 속도가 느려질 수 있음.
- Latency (대기 시간):
- 서버로부터 첫 번째 바이트를 받기까지의 시간.
- Latency가 높으면 네트워크 반응 속도가 느려질 수 있음.
- Speed (속도):
- 데이터 전송 속도를 바이트/초(B/s) 단위로 나타낸 것
데이터 사용량에 영향을 끼치는 항목들
- Requests (요청 데이터 크기):
- 클라이언트에서 서버로 전송된 총 요청 데이터의 크기.
- 요청 데이터 크기가 크면 네트워크 사용량이 증가.
- Responses (응답 데이터 크기):
- 서버에서 클라이언트로 전송된 총 응답 데이터의 크기.
- 응답 데이터 크기가 크면 네트워크 사용량이 증가.
- Combined (전체 데이터 크기):
- 요청과 응답 데이터의 총합.
1번 플로우 (메인)
| Category |
이전(메인) | 개선(메인) | Difference (ms/s/%) |
| Protocols | HTTP/1.1 | HTTP/1.1 | - |
| Completed | 30 | 5 | - |
| Incomplete | 58 | 31 | - |
| Failed | 1 | 0 | - |
| Blocked | 0 | 0 | - |
| DNS Requests | 42 | 9 | - |
| Connects | 88 | 36 | - |
| TLS Handshakes | 76 | 32 | - |
| Kept Alive | 0 | 0 | - |
| Start | 5/19/24 11:23:46 | 5/19/24 11:29:10 | - |
| End | 5/19/24 11:24:24 | 5/19/24 11:29:28 | - |
| Timespan | 37.92 s | 17.44 s | - |
| Requests / sec | 0.79 | 0.29 | - |
| Duration | 22h 42m | 9h 33m | Error |
| DNS Time | 804 ms | 111 ms | ~86.19% faster |
| Connect Time | 5.62 s | 888 ms | ~84.20% faster |
| TLS Handshake Time | 8.29 s | 1.75 s | ~78.89% faster |
| Latency | 0 ms | 0 ms | - |
| Speed | 84 B/s | 224 B/s | ~166.67% faster |
| Request Speed | - | - | - |
| Response Speed | - | - | - |
| Requests Size | 923.73 KB | 455.91 KB | ~50.66% less |
| Responses Size | 5.61 MB | 6.90 MB | ~23.00% more |
| Combined Size | 6.51 MB | 7.34 MB | ~12.74% more |
| Compression | - | - | - |
2번 플로우 (검색)
| Category | 이전(검색) | 개선(검색) | Difference (ms/s/%) |
| Protocols | HTTP/1.1 | HTTP/1.1 | - |
| Requests | |||
| Completed | 16 | 4 | - |
| Incomplete | 62 | 34 | - |
| Failed | 0 | 0 | - |
| Blocked | 0 | 0 | - |
| DNS | 31 | 7 | - |
| Connects | 78 | 38 | - |
| TLS Handshakes | 74 | 29 | - |
| Kept Alive | 0 | 0 | - |
| Timing | |||
| Start | 5/19/24 11:23:46 | 5/19/24 11:31:04 | - |
| End | 5/19/24 11:25:54 | 5/19/24 11:31:17 | - |
| Timespan | 2 m 7 s | 13.73 s | - |
| Requests / sec | 0.13 | 0.29 | - |
| Duration | 13 h 7 m | 4 h 56 m | Error |
| DNS Time | 898 ms | 132 ms | ~85.30% faster |
| Connect Time | 3.56 s | 900 ms | ~74.72% faster |
| TLS Handshake Time | 6.43 s | 1.81 s | ~71.84% faster |
| Latency | 0 ms | 0 ms | - |
| Speed | 99 B/s | 80 B/s | ~19.19% slower |
| Request Speed | - | - | - |
| Response Speed | - | - | - |
| Size | |||
| Requests | 1.64 MB | 402.71 KB | ~75.45% less |
| Responses | 2.84 MB | 993.78 KB | ~65.01% less |
| Combined | 4.47 MB | 1.36 MB | ~69.56% less |
| Compression | - | - | - |
3번 플로우 (셔터)
| Category | 이전(셔터) | 개선(셔터) | Difference (ms/s/%) |
| Protocols | HTTP/1.1 | HTTP/1.1 | - |
| Requests | |||
| Completed | 20 | 14 | - |
| Incomplete | 51 | 30 | - |
| Failed | 0 | 2 | - |
| Blocked | 0 | 0 | - |
| DNS | 29 | 14 | - |
| Connects | 71 | 44 | - |
| TLS Handshakes | 70 | 42 | - |
| Kept Alive | 0 | 0 | - |
| Timing | |||
| Start | 5/19/24 11:23:46 | 5/19/24 11:31:04 | - |
| End | 5/19/24 11:26:49 | 5/19/24 11:32:14 | - |
| Timespan | 3 m 3 s | 1 m 9 s | - |
| Requests / sec | 0.11 | 0.20 | - |
| Duration | 19 h 49 m | 8 h 26 m | Error |
| DNS Time | 579 ms | 249 ms | ~57.00% faster |
| Connect Time | 2.66 s | 940 ms | ~64.66% faster |
| TLS Handshake Time | 5.61 s | 2.23 s | ~60.25% faster |
| Latency | 0 ms | 0 ms | - |
| Speed | 81 B/s | 78 B/s | ~3.70% slower |
| Request Speed | - | - | - |
| Response Speed | - | - | - |
| Size | |||
| Requests | 2.16 MB | 807.56 KB | ~63.78% less |
| Responses | 3.35 MB | 1.48 MB | ~55.82% less |
| Combined | 5.51 MB | 2.27 MB | ~58.81% less |
| Compression | - | - | - |
종합 (1 + 2 + 3)
| Category | 이전 | 개선 | Difference (%) |
| Completed | 22.00 | 7.67 | 65.15 |
| Incomplete | 57.00 | 31.67 | 44.44 |
| Failed | 0.33 | 0.67 | -100.00 |
| DNS | 34.00 | 10.00 | 70.59 |
| Connects | 79.00 | 39.33 | 50.21 |
| TLS Handshakes | 73.33 | 34.33 | 53.18 |
| DNS Time (ms) | 760.33 | 164.00 | 78.43 |
| Connect Time (s) | 3.95 | 0.91 | 76.96 |
| TLS Handshake Time (s) | 6.78 | 1.93 | 71.52 |
| Speed (B/s) | 88.00 | 127.33 | -44.70 |
| Requests (MB) | 1.57 | 0.56 | 64.73 |
| Responses (MB) | 3.93 | 3.12 | 20.56 |
| Combined (MB) | 5.50 | 3.66 | 33.47 |
3.1.2. Xcode Instruments (CPU 사용량, 메모리 사용량)
► 주요 항목 설명
Time Profiler → CPU Usage → Weight: 총 시간 중 해당 함수가 차지하는 비율. Weight가 높을수록 CPU 사용량이 많음.
Allocations → Total Bytes: 프로그램 실행 동안 총 할당된 메모리의 양
1번 플로우 (메인)


2번 플로우 (검색)


3번 플로우 (셔터)


4. 한계 및 개선 방안
4.1. 한계
- 내부 코드에 접근할 수 없어 자동화 된 UITests를 진행하지 못한 점이 아쉽다. 따라서 오차가 있을 수 있다.
- Xcode Instruments 분석 결과의 경우 실제 올리브영 앱의 내부 코드와 캐싱 방법에 따라 결과가 크게 달라질 가능성이 있다.
- 3번 플로우에서 스택 구조가 깊어짐에 따라 발생하는 메모리 문제 해결까지는 나아가지 못 했다.
- 제한된 일정으로 일부 리디렉팅 문제 등이 잔존해있다.
- 한정된 플로우만을 이용해서 측정했다.
4.2. 개선 방안
- 자동화된 UITests를 진행할 것
- 스택 구조가 깊어지는 경우 발생하는 메모리 문제를 해결할 것
- 웹 ↔ 앱 통신 방식으로 UINavigationController를 관리할 것
- 다양한 플로우로 테스트할 것
5. 결론
- UINavigationController를 적용하면 특정 플로우에서 네트워크 사용량을 33.5% 절감할 수 있다.
- 올리브영 앱 내부 구조, WKWebView, UINavigationController, 웹과 네이티브 앱의 통신 방식을 깊이 이해할 수 있었다.
참고
https://babbab2.tistory.com/51
https://velog.io/@gnwjd309/iOS-WKWebView
https://forums.developer.apple.com/forums/thread/699936
https://developer.apple.com/videos/play/wwdc2019/411/
https://techblog.gccompany.co.kr/charles-proxy-소개-4c4a3bbc8994
'Dev > Project' 카테고리의 다른 글
| 올리브영 iOS 앱 구조 개선 프로젝트 2.0 + WKScriptMessageHandler, UI Test (2) | 2024.06.13 |
|---|