WKWebView导致我的视图控制器泄漏
我的视图控制器显示一个WKWebView。 我安装了一个消息处理程序,一个很酷的Web Kitfunction,可以让我的代码从网页内部得到通知:
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 }
看起来,仅仅将自己安装为消息处理程序会导致保留周期,从而导致泄漏!
像往常一样正常,周五国王。 事实certificate,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")
有用! 现在叫deinit
,certificate没有泄漏。 看起来这不应该起作用,因为我们创build了我们的LeakAvoider对象,从来没有提及它; 但请记住,WKUserContentController本身正在保留它,所以没有问题。
为了完整deinit
,现在调用了deinit
,可以在那里卸载消息处理程序,但是我不认为这实际上是必须的:
deinit { println("dealloc") self.wv.stopLoading() self.wv.configuration.userContentController.removeScriptMessageHandlerForName("dummy") }
Matt发布的解决scheme正是您所需要的。 以为我会把它翻译成客观的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"];
泄漏是由userContentController.addScriptMessageHandler(self, name: "handlerName")
,它将保留对消息处理程序self
的引用。
为了防止泄漏,只需在不再需要时通过userContentController.removeScriptMessageHandlerForName("handlerName")
删除消息处理程序。 如果在viewDidAppear
添加addScriptMessageHandler,则最好在viewDidDisappear
中将其删除。