2014-10-15 1 views
40

내보기 컨트롤러에 WKWebView가 표시됩니다. 내 코드는 웹 페이지 내부에서 통보받을 수 있습니다 메시지 처리기, 멋진 웹 키트 기능 설치 :WKWebView로 인해 내보기 컨트롤러가 누출됩니다.

override func viewDidAppear(animated: Bool) { 
    super.viewDidAppear(animated) 
    let url = // ... 
    self.wv.loadRequest(NSURLRequest(URL:url)) 
    self.wv.configuration.userContentController.addScriptMessageHandler(
     self, name: "dummy") 
} 

func userContentController(userContentController: WKUserContentController, 
    didReceiveScriptMessage message: WKScriptMessage) { 
     // ... 
} 

지금까지 너무 좋아,하지만 지금은 내보기 컨트롤러가 누출되는 것을 발견했습니다 -시 이 해제 될 예정이다, 그것은되지 않습니다 :

deinit { 
    println("dealloc") // never called 
} 

단순히 메시지 핸들러로 자신을 설치하는 것은이주기 때문에 누수를 유지하는 원인이 나타납니다!

답변

80

평소대로 왕 금요일. WKUserContentController 은 메시지 처리기을 유지합니다. 이는 메시지 처리기가 사라진 경우 메시지 처리기로 메시지를 보내지 못하기 때문에 어느 정도 의미가 있습니다. CAAnimation이 대리자를 유지하는 방식과 유사합니다.

그러나 WKUserContentController 자체가 유출되기 때문에 유지주기가 발생합니다. 그것은별로 중요하지 않습니다 (16K 만),보기 컨트롤러의 유지주기와 누수가 좋지 않습니다.

제 해결 방법은 trampoline 개체를 WKUserContentController와 메시지 처리기 사이에 삽입하는 것입니다. trampoline 객체는 실제 메시지 처리기에 대한 약한 참조 만 가지므로 유지주기가 없습니다. 여기에 트램 폴린 객체는 다음과 같습니다

class LeakAvoider : NSObject, WKScriptMessageHandler { 
    weak var delegate : WKScriptMessageHandler? 
    init(delegate:WKScriptMessageHandler) { 
     self.delegate = delegate 
     super.init() 
    } 
    func userContentController(userContentController: WKUserContentController, 
     didReceiveScriptMessage message: WKScriptMessage) { 
      self.delegate?.userContentController(
       userContentController, didReceiveScriptMessage: message) 
    } 
} 

우리가 메시지 처리기를 설치할 때 지금, 우리는 self 대신 트램 폴린 객체를 설치

self.wv.configuration.userContentController.addScriptMessageHandler(
    LeakAvoider(delegate:self), name: "dummy") 

의미가 있습니다! 이제 deinit이 호출되어 누출이 없음을 증명합니다. 우리가 LeakAvoider 객체를 생성했기 때문에 이것이 작동해서는 안되는 것처럼 보입니다. 그러나 WKUserContentController 자체가이를 유지하므로 아무런 문제가 없음을 기억하십시오.

deinit { 
    println("dealloc") 
    self.wv.stopLoading() 
    self.wv.configuration.userContentController.removeScriptMessageHandlerForName("dummy") 
} 
+0

깜짝 높지 upvote에 있습니다. 큰 도움. – Nick

+0

어떤 종류의 영혼이라도 이것을 objectivec 동등한 코드로 번역 할 수 있습니까? – mkto

+0

@mkto - 구현의 obj-c 버전을 게시했습니다. – johan

12

가 누출에 대한 참조를 유지합니다 userContentController.addScriptMessageHandler(self, name: "handlerName")에 의해 발생 :이 실제로 필요하다 생각하지 않습니다하지만 완성도를 들어

, 지금 deinit를 호출 할 때, 당신은 거기에 메시지 처리기를 제거 할 수 있습니다 메시지 처리기 self.

누출을 방지하려면 더 이상 필요하지 않을 때 userContentController.removeScriptMessageHandlerForName("handlerName")을 통해 메시지 처리기를 제거하면됩니다. viewDidAppear에 addScriptMessageHandler를 추가하면 viewDidDisappear에서 제거하는 것이 좋습니다.

+0

"더 이상 필요가 없을 때"문제는 : 언제입니까? 이상적으로 그것은 당신의 뷰 컨트롤러의'deinit' (Objective-C'dealloc')에있을 것이지만, 우리가 유출되고 있기 때문에 결코 호출되지 않습니다! 그것이 트램폴린 솔루션으로 해결할 수있는 문제입니다. 그건 그렇고,이 같은 문제와 동일한 해결책은 iOS 9로 계속됩니다. – matt

+0

정말 사용 사례에 따라 다릅니다. presentViewController를 통해 프리젠 테이션을 표시하면 시간을 종료한다고 가정 해보십시오. 당신이 그것을 nav view controller로 밀어 넣을 때, 당신이 그것을 팝 할 때입니다. WKWebView는 자신을 보유하고 있기 때문에 결코 deinit를 호출하지 않으므로 deinit가되지 않습니다. – siuying

+0

앞서 언급했듯이, viewDidAppear에서 addScriptMessageHandler를 호출하면 viewDidDisapper의 반대 removeScriptMessageHandlerForName이 작동합니다. – siuying

13

matt가 게시 한 해결책은 필요한 것입니다. 내가 목표 - C 코드로 변환 할 거라고 생각

@interface WeakScriptMessageDelegate : NSObject<WKScriptMessageHandler> 

@property (nonatomic, weak) id<WKScriptMessageHandler> scriptDelegate; 

- (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate; 

@end 

@implementation WeakScriptMessageDelegate 

- (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate 
{ 
    self = [super init]; 
    if (self) { 
     _scriptDelegate = scriptDelegate; 
    } 
    return self; 
} 

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message 
{ 
    [self.scriptDelegate userContentController:userContentController didReceiveScriptMessage:message]; 
} 

@end 

다음과 같이 그것의 사용을합니다

WKUserContentController *userContentController = [[WKUserContentController alloc] init];  
[userContentController addScriptMessageHandler:[[WeakScriptMessageDelegate alloc] initWithDelegate:self] name:@"name"]; 
관련 문제