안녕하세요, Client Team iOS 개발자 박중규입니다.

네이티브와 웹이 공존하는 하이브리드 환경에서 웹뷰의 성능은 곧 사용자 경험을 의미합니다. 이번 글에서는 1편에서 소개한 선언형 웹뷰 아키텍처를 바탕으로, 최적의 사용자 경험을 만들기 위해 마주했던 주요 기술적 문제와 해결 과정을 공유합니다.

앱에 웹뷰를 포함한다는 것은 단순히 웹 화면을 띄우는 것이 아니라, 웹과 앱을 하나의 서비스처럼 자연스럽게 동작하게 만드는 일입니다. 사용자는 웹과 앱을 구분하지 않기 때문에, 두 환경을 자연스럽게 연결하는 일관된 경험이 필요합니다.

하이브리드 환경을 구현하기 위해서는 상태 동기화, 일관된 UX, 앱-웹 통신, 디버깅 체계 등 일반적인 네이티브 개발과는 다른 과제들을 마주하게 됩니다. 따라서 최적의 경험을 제공하기 위해서는 다음과 같은 기술적 어려움이 따릅니다.

웹 화면이라도 사용자는 네이티브와 같은 속도와 자연스러움을 기대합니다. 이를 위해 UI 차이를 줄이는 일관된 디자인 시스템의 적용부터, 웹뷰의 로딩 속도 개선과 앱 생명주기와의 정렬 같은 최적화가 필요합니다. 이질감을 줄이는 작은 차이들이 결국 사용자 경험을 완성합니다.

웹과 네이티브는 브릿지 통신이나 쿠키 등으로 상태 데이터를 공유합니다. 단순한 상태의 공유를 넘어, 어떤 데이터를 언제, 누가 갱신할지에 대해 정밀한 소통이 필요합니다. 특히 인증처럼 무결성이 중요한 정보는 실시간 감시와 복구가 가능한 구조로 관리해야 합니다.

WKWebView는 SwiftUI 환경에서 독립적으로 다뤄야 합니다. 선언형 구조로 개선하면서 확장성과 재사용성을 확보했고 SwiftUI 문맥에도 매끄럽게 적용할 수 있었지만 그만큼 설계 복잡도가 필연적으로 상승하기 때문에 코드 관리와 성능 관리 역시 더 어려워질 수 있습니다.

웹뷰에서 버그가 발생했을 때 웹과 앱 중 어느 부분에서 문제가 발생했는지 즉시 판단하기 어려운 경우가 많습니다. 단순한 문제라도 네이티브에서 발생했다면 혼자 빠르게 해결할 수 있지만, 웹뷰에서는 웹과의 협업이 필요하기 때문에 디버깅에 더 많은 리소스가 들 수 있습니다.

이러한 어려움들을 해결하기 위해서는 다양한 관점에서의 접근이 필요합니다. 아래에서는 이를 바탕으로 실제 제품에 적용한 개선 방식과 사례를 차례로 소개합니다.

웹뷰 도입 초기, 가장 먼저 마주한 문제는 로딩 속도였습니다. WKWebView는 네이티브와 렌더링 방식이 달라, 웹 코드 구조·디바이스 성능·네트워크 상태에 따라 속도가 크게 달라집니다. 이 변수를 최소화해 항상 일관된 속도를 제공하는 것이 목표였습니다.

특히 일부 저사양 기기나 네트워크 환경에서는 로딩 지연이 심해 사용자가 오류로 인식하거나 앱이 멈춘 것으로 오해하는 사례도 있었다는 점이 문제의식을 더욱 강화했습니다. 사용자는 화면이 늦게 뜨는 이유를 알 수 없기 때문에, 체감 속도 저하는 곧 불편함으로 이어졌습니다.

SwiftUI는 성능 최적화를 위해 필요한 경우 body를 계산하는 lazy 렌더링 구조를 따릅니다. 반면 WKWebView는 앱 라이프사이클과 독립적으로 사전 로드가 가능하기 때문에, 사용자 액션 이전에 리소스를 미리 불러와 즉시 표시할 수 있습니다.

  • 기존: 사용자 클릭 → 웹뷰 생성 → 리소스 다운로드 → 렌더링
  • 개선: 웹뷰 사전 생성 → 리소스 캐싱 → 사용자 클릭 → 즉시 표시

매번 새 웹뷰를 생성해 로드하는 방식은 비효율적이었습니다. 웹뷰 인스턴스와 주요 리소스를 미리 준비하고, 화면 진입 시 replace를 통해 빠르게 화면을 교체해 지연을 최소화했습니다.

  • Pre-load: 웹뷰와 정적 리소스를 사전 준비
  • Replace: 전체 리로드 대신 필요한 영역만 교체
  • Fallback: Replace 실패 시 일반 로드로 안전하게 복구

Replace 과정이 안정적으로 동작하려면 현재 상태를 정확히 파악할 수 있어야 합니다. 이를 위해 1편에서 소개한 로깅 시스템을 고도화해 상태 전이를 세분화하고, 성공·실패·타임아웃 등 주요 이벤트를 로깅에 포함했습니다. 덕분에 예외 상황에서도 안정적으로 동작하며, 문제 발생 시 원인 추적이 훨씬 용이해졌습니다.

선제적 로딩 구조를 적용한 결과, 로딩 속도에서 유의미한 개선이 확인되었습니다. 실제 사용자 피드백에서도 로딩 지연으로 인한 이탈이나 오류 인식이 거의 사라졌고, 다양한 환경에서도 최대 3초 이내에 안정적으로 화면을 확인할 수 있게 되어 체감 속도가 크게 향상되었습니다.

  • Best Case (Replace 성공)
    • 이전: 0.6~0.8초
    • 개선 후: 0.1~0.2초
  • Worst Case (Replace 실패 → Load)
    • 이전: 10초 이상(무한 로딩 가능성 존재)
    • 개선 후: 3-4초 (안정적 로딩 보장)

앱과 웹은 구조적으로 독립된 환경이지만, 사용자는 이를 하나의 서비스로 인식합니다. 따라서 인증 상태나 사용자 세션 같은 핵심 데이터가 두 환경에서 항상 일관되게 유지되어야 하며, 이를 위한 동기화 구조가 필요합니다.

공유 상태에 대한 동기화가 불안정하면 초기 세션 구성이나 인증 상태에 문제가 생길 수 있어, 사용자 경험에 직접적인 영향을 줄 수 있기 때문에 항상 예측 가능하고 안정적인 상태를 유지하는 것이 중요합니다.

상태 동기화를 위한 방식 중 하나로 WKWebView의 쿠키를 사용할 수 있습니다. 그러나 WKWebView 자체의 동작 특성상, 다음과 같은 제약이 존재합니다.

  1. 예상치 못한 상태 손실
    • OS 메모리 관리 등 특정 상황에서 쿠키가 예기치 않게 사라질 수 있음
    • 그 결과, 세션이 끊기거나 인증 상태가 불일치할 가능성이 있음
  1. 비동기 세팅 문제
    • 쿠키는 비동기적으로 세팅되므로, 세팅 완료 전에 웹뷰를 로드하면 초기 세션이 올바르게 구성되지 않을 수 있음

이러한 제약으로 인해, 쿠키의 상태를 실시간으로 감시하고 필요 시 복구할 수 있는 관리 체계가 필요합니다.

WKWebView의 쿠키는 앱 생명주기나 시스템 상태에 따라 예기치 않게 변경되거나 삭제될 수 있습니다. 하지만 인증 상태처럼 항상 무결성을 유지해야 하는 데이터는 이런 상황에서도 안정적으로 관리되어야 합니다.

이를 위해 WKHTTPCookieStore의 옵저버 API를 활용해 쿠키 변화를 실시간으로 감시하고, 문제가 발생하면 자동으로 복구하는 CookieMonitor 객체를 설계했습니다.

WebKit에서는 WKHTTPCookieStoreObserver를 통해 쿠키 변경을 감지할 수 있는 인터페이스를 제공합니다.

이 API들을 활용하여 다음과 같은 흐름을 구현할 수 있습니다:

  • 쿠키 값 변경 감지
  • 상태 유효성 검증
  • 이상 시 쿠키 복구 및 웹뷰 리로드

또한 앱이 background → foreground로 전환되는 시점은 메모리 상태가 불안정할 수 있기 때문에, 이 타이밍에도 쿠키 상태를 재점검합니다.

WKHTTPCookieStore의 쿠키 세팅은 비동기 처리이므로, 완료되기 전에 웹뷰를 로드하면 초기 세션이 올바르게 구성되지 않을 수 있습니다. 따라서 쿠키 세팅 완료 이후에만 웹뷰를 로드하도록 보장해야 합니다.

기존에는 SwiftUI의 선언형 스타일을 활용해 onAppear 시점에서 웹뷰를 로드했습니다.

이 방식은 쿠키에 의존하지 않는 웹뷰에서는 안전하게 동작하지만, 쿠키가 필요한 경우에는 세팅이 완료되기 전에 로드될 위험이 있습니다.

따라서 쿠키 세팅이 끝난 뒤에만 웹뷰를 로드하도록 구조를 개선해야 합니다.

  • ViewModel이 웹뷰 로드를 컨트롤 할 수 있도록 API 제공
  • WebViewConfigurationBuilder에서 쿠키 세팅의 completion 핸들러와 로드 호출을 연결

개선된 구조는 쿠키 세팅이 확실히 완료된 이후에만 loadWebView가 실행되므로, 웹뷰의 초기 세션 무결성을 안정적으로 보장할 수 있습니다.

웹뷰 구조가 커지고 세분화되면서 Builder, Bridge, Monitor, ViewModel 등 다양한 객체들이 얽히게 되었습니다. 객체 수가 늘고 의존 관계가 복잡해질수록 코드 구조 또한 점점 복잡해지고, 이로 인해 메모리와 성능을 안정적으로 관리하는 것이 더욱 중요해졌습니다. 따라서 이러한 복잡성을 관리하기 위해 Instruments를 활용해 문제를 조기에 발견하고자 했습니다.

Instruments의 Allocations는 앱 실행 중 생성·해제되는 객체의 흐름을 추적해, 메모리 누수나 retain cycle을 확인할 수 있는 도구입니다. 특히 아래 상황에서 유용하게 사용됩니다.

  • 화면 전환 후에도 해제되지 않는 객체 추적
  • 반복 진입 시 메모리 증가 패턴 확인
  • 특정 클래스나 프레임워크 중심 분석
  • 의도된 유지와 누수 여부 판단
컬럼명설명
Category객체가 생성된 클래스 또는 프레임워크
#Persistent여전히 메모리에 남아 있는 객체 수 (retain 의심 대상)
#Transient생성되었다 해제된 객체 수
Total Bytes누적 메모리 사용량
#Total객체가 생성된 총 횟수

구조가 복잡할수록 객체의 생명주기 추적이 어려워지기 때문에, Allocations는 문제를 빠르게 관찰하고 재현하는 데 적합한 도구입니다.

웹뷰 시스템의 성능을 검증하기 위해 실제 환경보다 높은 부하를 주는 테스트를 진행한 결과, 메모리 사용량이 누적되는 패턴을 확인했습니다.

Instruments를 활용해 추적한 결과, 웹뷰 관련 일부 인스턴스가 메모리에서 완전히 해제되지 않는 상황이 확인되었습니다.

여러 객체가 맞물려 동작하는 구조 속에서 참조의 흐름이 예상과 다르게 이어졌고, WKUserContentController와 메시지 핸들러 간의 순환 참조로 인해 ViewModel과 WKWebView가 해제되지 않는 현상이 발생했습니다.

이를 해결하기 위해 메시지 핸들러를 약한 참조(weak reference)로 감싸는 래퍼 구조를 도입해, 참조 사이클을 끊고 메모리가 안정적으로 해제되도록 개선했습니다.

참조 문제를 해결한 후 동일한 테스트 시나리오를 다시 수행했을 때, 메모리 사용량이 정상적으로 해제되는 것을 확인했습니다.

코드 구조가 복잡해질수록 문제의 원인을 조기에 파악하기 어려워지기 때문에, 개발 주기 안에 Instruments와 같은 도구를 포함해 지속적으로 살펴보는 과정이 중요합니다. 이를 통해 잠재적인 이슈를 빠르게 발견할 수 있습니다.

웹뷰에서 하얀 화면이 떠요! 무엇이 문제인가요?

웹뷰 개발을 하다 보면 종종 마주하는, 공감되는 상황입니다. 그러나 하이브리드 환경에서는 문제가 네이티브에 있는지, 웹에 있는지 바로 판단하기 어렵습니다. 단순해 보이는 이슈도 양쪽의 상태를 모두 살펴봐야 하며, 경우에 따라 웹팀과의 협업도 필요해 디버깅에 많은 리소스가 투입될 수 있습니다.

이런 상황을 줄이기 위해서는 어디서 어떤 문제가 발생했는지 빠르게 파악할 수 있도록, 보다 정교한 로깅 체계가 필요했습니다.

웹뷰 시스템은 네이티브 앱, 브라우저 프로세스, 쿠키 기반 세션, SwiftUI 뷰 트리 등 다양한 서브 시스템과 상호작용하며 작동합니다. 이처럼 많은 컴포넌트가 연결된 구조에서는 문제가 발생했을 때 원인을 단번에 파악하기 어렵습니다. 단순히 ‘웹뷰가 뜨지 않는다’는 로그만으로는 충분하지 않고, 어떤 단계에서 어떤 일이 일어났는지를 세분화해 기록하는 체계가 필요했습니다.

예를 들어, 하나의 웹뷰 화면이 사용자에게 보여질 때 다음과 같은 상태 전이가 발생합니다:

  • 웹뷰 로딩 사이클: 페이지가 정상적으로 로드되었는지, 실패했는지 등의 기본 상태 흐름
  • 쿠키 관리: 세션 유지를 위한 쿠키가 유효한지, 복구가 필요한지
  • Replace 흐름: 사전 준비된 웹뷰를 활용할 수 있었는지, Fallback이 발생했는지 등
  • 브릿지 통신: 웹과 네이티브 간 메시지 전달 및 응답 처리 흐름
  • 뷰 라이프사이클: SwiftUI뷰가 생성되거나 사라질 때 발생하는 UI 관점의 상태 변화
  • WebKit 내부 상태: 메모리와 같은 시스템 리소스나 프로세스 상태 등 외부 요인에 의한 실패 여부

따라서 상태 간 관계가 복잡하다 보니, 문제 흐름을 더 잘 파악할 수 있도록 구조적으로 기록하는 방식이 필요했습니다

각 서브 시스템에서 발생하는 상태 변화를 처리하기 위해, 우리는 웹뷰에 특화된 상태 관리자 레이어를 도입했습니다. 이 레이어는 상태 전이를 감지해 정의된 타입으로 변환하고, 상황에 따라 fallback 처리나 에러 화면 전환 등의 액션을 수행합니다. 이후 상태와 관련된 정보는 Sentry 등 외부 로깅 시스템에 함께 기록됩니다. 예를 들어, ‘웹뷰 로드 실패’ 상태가 감지된 경우에는 다음과 같은 처리가 이뤄집니다:

  • 상태 값을 업데이트하고
  • 사용자에게 에러 화면을 보여줄지, fallback 로드를 시도할지 판단하며
  • 실패 원인, 발생 시점, 네트워크 상태 등의 정보를 로그에 포함해 전송합니다.

로깅 시스템을 확장 가능하고 유지보수하기 쉬운 형태로 만들기 위해, 상태를 명확하게 정의하고 일관된 방식으로 다루는 것이 중요했습니다. 우리는 웹뷰에서 발생할 수 있는 상태들을 추상 타입으로 정리하고, 이를 기준으로 로깅 흐름을 구성했습니다. 이 방식은 다음과 같은 장점이 있습니다

  • 상태 정의가 명확해지고, 로깅 대상이 일관되게 구성됨
  • 분기 로직을 타입 기반으로 관리할 수 있어 유지보수가 쉬움
  • Sentry, Firebase, 자체 로그 서버 등 목적지에 관계없이 동일한 데이터 구조로 전송 가능
  • 새로운 상태나 로깅 도구가 추가되더라도 기존 구조에 큰 변경 없이 대응 가능

아래 이미지는 실제 제품에서 수집 중인 로깅 예시입니다.

상태 기반 로깅 구조를 통해 다양한 경계에서 발생하는 이벤트를 수집할 수 있게 되었지만, 단순히 상태 정보만으로는 충분하지 않은 경우도 많습니다. 실제 문제를 빠르게 파악하고 적절히 대응하기 위해서는, 해당 상황이 발생한 맥락 정보까지 함께 기록하는 것이 중요합니다.

초기에는 url, params, status, error, createdAt 등 최소한의 정보만을 중심으로 로깅을 구성했습니다. 하지만 웹뷰의 사용 범위가 넓어지고 시나리오가 복잡해지면서, 이러한 정보만으로는 원인을 명확히 식별하기 어려운 경우가 늘어났습니다.

예를 들어, ‘웹뷰 로딩 실패’라는 상태만 로그에 남아 있을 경우, 실제로는 다음과 같은 다양한 원인이 존재할 수 있습니다:

  • WKWebView 내부 프로세스 오류
  • 사용자 기기의 네트워크 연결 문제
  • 네이티브 사이드의 데이터 준비 누락
  • 웹 코드에서의 예외 발생

이처럼 동일한 증상이라도 원인에 따라 대응 방식이 달라지기 때문에, 로깅에는 가능한 한 풍부한 맥락 정보를 포함하는 것이 중요합니다.

예를 들어 웹 페이지 로딩 실패의 경우, WebKit이 제공하는 WKNavigationDelegate의 콜백을 통해 보다 구체적인 오류 정보를 수집할 수 있습니다:

이 콜백의 Error 객체는 런타임에 Objective-C 기반의 NSError 타입으로 전달되며, 다음과 같은 유용한 정보를 포함합니다:

  • code: 에러 코드
  • domain: 에러의 도메인 (예: NSURLErrorDomain, WebKitErrorDomain 등)
  • localizedDescription: 사용자에게 노출할 수 있는 설명
  • underlyingErrors: 중첩된 에러 정보

이러한 정보들을 함께 기록함으로써 단순한 실패 여부를 넘어서, 자세한 맥락을 기록하여 디버깅이나 분석 과정에서 유용한 단서를 확보할 수 있습니다.

아래는 실제 서비스 환경에서 수집 된 두 개의 사례입니다. 둘 다 load-request-failed 상태로 분류되지만, 원인은 전혀 다릅니다.

(1) 네트워크 연결 시간 초과 (NSURLErrorTimedOut)

(2) WebKit 내부 프로세스 충돌 (WebKitErrorDomain)

이처럼 웹뷰 구조가 복잡해질수록 단순한 상태 정보만으로는 문제를 파악하기 어렵습니다. 로깅은 실행 흐름과 컨텍스트를 이해하는 데 중요한 역할을 하며, 이를 위해 초기에 필요한 정보를 쌓을 수 있는 구조를 갖추고 유연하게 확장 가능한 형태로 유지하는 것이 필요합니다.

1·2편에 걸쳐 하이브리드 환경에서 웹뷰를 다루며 사용자 경험을 개선하기 위한 다양한 기술적 시도를 소개했습니다. Client Team은 ‘요구사항을 구현하는 팀’을 넘어 ‘더 나은 경험을 설계하는 팀’으로 성장하기 위해 아키텍처 설계부터 도구 선택, 실험에 이르기까지 여러 방향에서 고민을 이어왔습니다.

이 여정은 자연스럽게 더 깊은 기술적 구조와 운영 전략으로 확장되고 있습니다. 앞으로는 이 과정에서 얻은 기술적 인사이트와 개선 경험을 중심으로 한 이야기를 나누고자 합니다.

많은 기대와 관심 부탁드립니다.