[iOS, Objective-c] WKWebView ScriptMessageHandler Memory Leak

less than 1 minute read

addScriptMessageHandler


@interface WebViewController() <WKScriptMessageHandler>

...

@end

@implementation WebViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    WKWebViewConfiguration *sConfiguration      = [[WKWebViewConfiguration alloc] init];
    WKUserContentController *sContentController = [[WKUserContentController alloc] init];

    [sContentController addScriptMessageHandler:self name:@"message"];
    [sConfiguration     setUserContentController:sContentController];

    self.webview = [[WKWebView alloc] initWithFrame:CGRectZero configuration:sConfiguration];

    ...
}

...

- (void)userContentController:(nonnull WKUserContentController *)userContentController didReceiveScriptMessage:(nonnull WKScriptMessage *)message
{
  ...
}

@end

Memory leak

WKUserContentController *sContentController = [[WKUserContentController alloc] init];
[sContentController addScriptMessageHandler:self name:@"message"];
  • the WKUserContentController holds a strong reference to the message handler self
  • so it causes a retain cycle

Solution #1

  • remove the message handler
self.webview.configuration.userContentController.removeScriptMessageHandlerForName("message")

Solution #2

  • create a object to solve the retain cycle

  • 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

// add message hanlder
WKUserContentController *sContentController = [[WKUserContentController alloc] init];
[sContentController addScriptMessageHandler:[[WeakScriptMessageDelegate alloc] initWithDelegate:self] name:@"message"];
  • swift
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)
    }
}

// add message hanlder
self.webview.configuration.userContentController.addScriptMessageHandler(LeakAvoider(delegate:self), name: "message")

Reference

Leave a comment