2014-10-15 11 views
40

Görünüm denetleyicim bir WKWebView görüntülüyor. Benim kod web sayfası içinden bildirilmesini sağlayan bir ileti işleyicisi, serin Web Takımı özelliğini yüklü:WKWebView, görünüm denetleyicimin sızmasına neden oluyor

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

Şimdiye kadar iyi, ama şimdi benim bakış denetleyicisi sızıntı tesbit ettik - ne zaman o ayırmanın gerekiyordu, öyle değil mi:

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

sadece bir mesaj işleyicisi olarak kendimi yüklerken bir döngü ve dolayısıyla sızıntı muhafaza neden olduğunu görünür!

cevap

80

Her zamanki gibi düzeltin, King Friday. WKUserContentController , ileti işleyicisini korur. Bu, mesaj işleyicisinin varlığının sona ermesi durumunda mesaj işleyicisine neredeyse hiç mesaj gönderemediğinden belirli bir anlam ifade eder. Örneğin, CAAnimation'ın temsilcisini koruma biçimine paraleldir. Bununla birlikte, WKUserContentController'ın kendisi sızdırıyor olduğundan, aynı zamanda bir tutma döngüsüne de neden olur. Üste | Bu, kendi başına çok önemli değil (yalnızca 16K), ancak görüntü denetleyicinin tutma döngüsü ve sızıntısı kötü.

Benim geçici çözümüm, WKUserContentController ve ileti işleyicisi arasında bir trambolin nesnesini yerleştirmektir. Trambolin nesnesi, gerçek mesaj işleyicisine sadece zayıf bir referans gösterir, bu nedenle tutma döngüsü yoktur. İşte trambolin nesne:

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

biz mesajı işleyicisi yüklediğinizde Şimdi, self yerine trambolin nesnesini yükleyin:

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

Çalışıyor! Artık sızıntı olmadığını kanıtlayan deinit adı verilir. Bu işe yaramaz gibi gözüküyor, çünkü LeakAvoider nesnesini yarattık ve hiçbir zaman ona referans göstermedik; ama WKUserContentController'ın kendisini koruduğunu unutmayın, bu yüzden sorun yok.

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

Hayrete yüksek olmayan upvote. Büyük yardım – Nick

+0

herhangi bir ruh, bunu objectivec eşdeğer kodlarına çevirebilir mi? – mkto

+0

@mkto - Uygulamanın bir obj-c sürümü yayınlandı. – johan

12

kaçak bir başvuru tutacak userContentController.addScriptMessageHandler(self, name: "handlerName") kaynaklanır: Ben bu aslında gerekli olduğunu düşünmüyorum gerçi Bütünlüğü için

, şimdi deinit denir, orada ileti işleyicisi kaldırabilirsiniz mesaj işleyicisine self.

Sızıntıları önlemek için, artık gerekmediğinde ileti işleyiciyi userContentController.removeScriptMessageHandlerForName("handlerName") aracılığıyla kaldırın. AddScriptMessageHandler'ı viewDidAppear'a eklerseniz, onu viewDidDisappear'dan kaldırmak iyi bir fikirdir.

+0

"Artık ihtiyacınız olduğunda" Sorun şu: Bu ne zaman? İdeal olarak sizin kontrolörünüzün 'deinit' (Objective-C 'dealloc') 'unda olurdu, ama asla çağrılmaz çünkü (bekle) biz sızdırıyoruz! Bu benim trambolin çözümünün çözdüğü sorun. Bu arada, aynı sorun ve aynı çözüm iOS 9'a devam ediyor. – matt

+0

Gerçekten de kullanım durumunuza bağlı. Eğer presentViewController aracılığıyla sunup sunmadığınızı söyle, zaman işten attığınız zamandır. Bir nav görüntüleyici denetleyicisine bastığınızda, zaman onu açtığınız zamandır. WKWebView asla kendini koruduğu için deinit'i asla aramayacağı için deinit olmayacaktır. – siuying

+0

Daha önce de belirttiğim gibi, viewDidAppear uygulamasında addScriptMessageHandler'ı çağırdıysanız, viewDidDisapper uygulamasında ters removeScriptMessageHandlerForName işlevini kullanın. – siuying

13

Materyal tarafından gönderilen çözüm sadece ihtiyaç duyulan şeydir. Ben objektif-c koduna çevirmek sanıyordun

@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 

Sonra böyle onu kullanmakta:

WKUserContentController *userContentController = [[WKUserContentController alloc] init];  
[userContentController addScriptMessageHandler:[[WeakScriptMessageDelegate alloc] initWithDelegate:self] name:@"name"]; 
İlgili konular