반응형
Notice
Recent Posts
Recent Comments
Link
Today
Total
07-18 06:00
«   2025/07   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Archives
관리 메뉴

iOS 개발 기록 블로그

[iOS] WKWebView에서 발생하는 retain cycle과 메모리 릭 해결하기 본문

iOS

[iOS] WKWebView에서 발생하는 retain cycle과 메모리 릭 해결하기

crazydeer 2025. 5. 10. 20:07
반응형

WKWebView, 왜 갑자기 메모리 릭이 생기지?

WKWebView를 개발하면서 통칭 '브릿지'라고 하는 기능을 담당하는 WKScriptMessageHandler를 사용하여 브릿지를 네이티브에서 추가해주게 됩니다.

 

WKWebView를 사용할 때 WKScriptMessageHandler를 사용해 JavaScript 브릿지를 연결하다 보면, 어느 순간 deinit이 호출되지 않고 VC가 메모리에 남아 있는 걸 발견하게 됩니다.

 

contentController.add(self, name: "myBridge")

 

위처럼 추가해주고 저 같은 경우에 기존 방식으로 deinit 시점에 아래와 같이 수동으로 제거해주곤 합니다.

webView.configuration.userContentController.removeAllScriptMessageHandlers()

 

문제 원인: WebView가 VC를 retain하고 있다!

WebKit은 add(_:name:)로 넘겨준 객체(self, 즉 VC)를 강하게 참조합니다.
그리고 VC는 WebView를 서브뷰로 갖고 있죠.

 

즉, 이런 구조가 만들어집니다.

VC (나 자신) ───▶ WebView ───▶ 나 자신
      ▲                         │
      └────── retain cycle ─────┘

 

순환 참조(Retain Cycle) 발생.
그래서 deinit이 호출되지 않고, removeScriptMessageHandler()를 해줘도 늦는 경우가 많습니다.

 

 

 

해결 방법: Weak Proxy 패턴 도입하기

VC를 직접 넘기지 말고, 중간에 weak 참조만 가지는 래퍼를 끼워줍니다.

VC (진짜 나) ◀─── WeakScriptMessageHandler ◀─── WebView
     ▲                        │
     └──── weak reference ────┘

 

WeakScriptMessageHandler 클래스

final class WeakScriptMessageHandler: NSObject, WKScriptMessageHandler {
    weak var delegate: WKScriptMessageHandler?

    init(_ delegate: WKScriptMessageHandler) {
        self.delegate = delegate
    }

    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        delegate?.userContentController(userContentController, didReceive: message)
    }
}

 

사용

let contentController = WKUserContentController()
contentController.add(WeakScriptMessageHandler(self), name: "myBridge")

이 방법은 WebKit이 handler를 강하게 유지하더라도, 실질적으로 retain 되는 객체는 WeakScriptMessageHandler이므로 VC는 해제됩니다.

 

 

결과

  • ✅ VC가 더 이상 WebView에 의해 retain되지 않음
  • ✅ deinit이 정상적으로 호출됨
  • ✅ removeScriptMessageHandler()를 따로 호출할 필요 없음
  • ✅ 사이드 이펙트 없이 안정적

 

요약

VC retain 여부 WebView가 VC를 강하게 retain Weak proxy가 VC를 약하게 참조
retain cycle 발생 발생하지 않음
removeScriptMessageHandler 필요함 불필요
안정성 낮음 높음

 

 

 

이 패턴은 WebView 뿐 아니라 delegate 패턴에서 순환 참조를 피할 때도 널리 사용됩니다.
WKWebView를 자주 사용하는 앱이라면 이 방식을 공통 컴포넌트로 만들어두는 것도 좋습니다.

반응형