85

私のView ControllerはWKWebViewを表示します。メッセージ ハンドラーをインストールしました。これは、コードが Web ページ内から通知されるようにするクールな Web Kit 機能です。

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) {
        // ...
}

これまでのところは問題ありませんが、View Controller がリークしていることに気付きました。

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

自分自身をメッセージ ハンドラーとしてインストールするだけで保持サイクルが発生し、リークが発生するようです。

4

7 に答える 7

159

いつものように正解です、キング・フライデー。WKUserContentControllerがメッセージ ハンドラを保持していることがわかります。メッセージハンドラーが存在しなくなった場合、メッセージハンドラーにメッセージを送信することはほとんどできないため、これはある程度理にかなっています。たとえば、CAAnimation がデリゲートを保持する方法と同様です。

ただし、WKUserContentController 自体がリークしているため、保持サイクルも発生します。それ自体は大した問題ではありませんが (16K しかありません)、保持サイクルとビュー コントローラーのリークが問題です。

私の回避策は、WKUserContentController とメッセージ ハンドラーの間にトランポリン オブジェクトを挿入することです。トランポリン オブジェクトには、実際のメッセージ ハンドラーへの弱い参照しかないため、保持サイクルはありません。トランポリン オブジェクトは次のとおりです。

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")

できます!Nowdeinitが呼び出され、漏れがないことを証明します。LeakAvoider オブジェクトを作成し、それへの参照を保持していないため、これはうまくいかないようです。ただし、WKUserContentController 自体がそれを保持しているため、問題はありません。

完全を期すために、それdeinitが呼び出されたので、そこでメッセージハンドラーをアンインストールできますが、これは実際には必要ではないと思います:

deinit {
    println("dealloc")
    self.wv.stopLoading()
    self.wv.configuration.userContentController.removeScriptMessageHandlerForName("dummy")
}
于 2014-10-15T12:47:41.433 に答える
34

リークはuserContentController.addScriptMessageHandler(self, name: "handlerName")、メッセージ ハンドラへの参照を保持することによって発生しますself

リークを防ぐには、userContentController.removeScriptMessageHandlerForName("handlerName")不要になったときにメッセージ ハンドラ経由で単純に削除します。addScriptMessageHandler を に追加した場合は、 でviewDidAppear削除することをお勧めしviewDidDisappearます。

于 2015-09-07T17:14:11.237 に答える
21

マットによって投稿されたソリューションは、まさに必要なものです。私はそれをobjective-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"];
于 2015-10-27T10:26:47.313 に答える