2015-07-09 67 views
2

我們正在爲iOS構建瀏覽器。我們決定嘗試使用自定義的NSURLProtocol子類來實現我們自己的緩存方案並執行用戶代理欺騙。它能夠完成這兩件事情......問題在於,導航到某些網站(msn.com是最糟糕的)會導致整個應用程序的用戶界面凍結達15秒。顯然有些東西阻塞了主線程,但它不在我們的代碼中。NSURLProtocol + UIWebView +某些域=應用程序UI凍結

此問題只出現在UIWebView和自定義協議的組合中。如果我們換一個WKWebView(我們因各種原因不能使用),問題就消失了。同樣,如果我們沒有註冊協議,以至於它沒有被利用,問題就會消失。

它也似乎沒有太大的問題什麼協議;我們寫了一個毫無意義的虛擬協議,只是轉發響應(帖子的底部)。我們將該協議放入沒有任何其他代碼的裸機測試瀏覽器中 - 結果相同。我們也嘗試使用別人的(RNCachingURLProtocol)並觀察到相同的結果。看起來,這兩個組件與某些頁面的簡單組合會導致凍結。我無法嘗試解決(甚至調查)此問題,並且非常感謝任何指導或提示。謝謝!

import UIKit 

private let KEY_REQUEST_HANDLED = "REQUEST_HANDLED" 

final class CustomURLProtocol: NSURLProtocol { 
    var connection: NSURLConnection! 

    override class func canInitWithRequest(request: NSURLRequest) -> Bool { 
     return NSURLProtocol.propertyForKey(KEY_REQUEST_HANDLED, inRequest: request) == nil 
    } 

    override class func canonicalRequestForRequest(request: NSURLRequest) -> NSURLRequest { 
     return request 
    } 

    override class func requestIsCacheEquivalent(aRequest: NSURLRequest, toRequest bRequest: NSURLRequest) -> Bool { 
     return super.requestIsCacheEquivalent(aRequest, toRequest:bRequest) 
    } 

    override func startLoading() { 
     var newRequest = self.request.mutableCopy() as! NSMutableURLRequest 
     NSURLProtocol.setProperty(true, forKey: KEY_REQUEST_HANDLED, inRequest: newRequest) 
     self.connection = NSURLConnection(request: newRequest, delegate: self) 
    } 

    override func stopLoading() { 
     connection?.cancel() 
     connection = nil 
    } 

    func connection(connection: NSURLConnection!, didReceiveResponse response: NSURLResponse!) { 
     self.client!.URLProtocol(self, didReceiveResponse: response, cacheStoragePolicy: .NotAllowed) 
    } 

    func connection(connection: NSURLConnection!, didReceiveData data: NSData!) { 
     self.client!.URLProtocol(self, didLoadData: data) 
    } 

    func connectionDidFinishLoading(connection: NSURLConnection!) { 
     self.client!.URLProtocolDidFinishLoading(self) 
    } 

    func connection(connection: NSURLConnection!, didFailWithError error: NSError!) { 
     self.client!.URLProtocol(self, didFailWithError: error) 
    } 
} 
+0

嗨@Reid你找到了解決這個問題的方法嗎?我也有同樣的問題。 – Weizhi

+0

@Reid,有什麼消息嗎?我有同樣的問題。 –

+0

我不是那個我遇到這個問題的項目(甚至在那家公司),所以沒有在幾個月內看過它。使用CustomHTTPProtocol示例代碼(請參閱下面的@鮑里斯的答案)確實解決了這個問題,但我從來沒有能夠(a)完全隔離他們做了什麼,或者(b)使他們的協議適應我需要做的事情......但其中一種方法是你最好的選擇。 –

回答

1

我剛剛檢查NSURLProtocol行爲與msn.com,發現在某些時候在WebCoreSynchronousLoaderRunLoopMode模式稱爲startLoading方法。這會導致主線程阻塞。

翻閱CustomHTTPProtocol Apple sample code,我發現了描述這個問題的評論。修復是在下一步實施:

@interface CustomHTTPProtocol() <NSURLSessionDataDelegate> 

@property (atomic, strong, readwrite) NSThread * clientThread; ///< The thread on which we should call the client. 

/*! The run loop modes in which to call the client. 
* \details The concurrency control here is complex. It's set up on the client 
* thread in -startLoading and then never modified. It is, however, read by code 
* running on other threads (specifically the main thread), so we deallocate it in 
* -dealloc rather than in -stopLoading. We can be sure that it's not read before 
* it's set up because the main thread code that reads it can only be called after 
* -startLoading has started the connection running. 
*/ 
@property (atomic, copy, readwrite) NSArray * modes; 

- (void)startLoading 
{ 
    NSMutableArray *calculatedModes; 
    NSString *currentMode; 

    // At this point we kick off the process of loading the URL via NSURLSession. 
    // The thread that calls this method becomes the client thread. 

    assert(self.clientThread == nil); // you can't call -startLoading twice 

    // Calculate our effective run loop modes. In some circumstances (yes I'm looking at 
    // you UIWebView!) we can be called from a non-standard thread which then runs a 
    // non-standard run loop mode waiting for the request to finish. We detect this 
    // non-standard mode and add it to the list of run loop modes we use when scheduling 
    // our callbacks. Exciting huh? 
    // 
    // For debugging purposes the non-standard mode is "WebCoreSynchronousLoaderRunLoopMode" 
    // but it's better not to hard-code that here. 

    assert(self.modes == nil); 
    calculatedModes = [NSMutableArray array]; 
    [calculatedModes addObject:NSDefaultRunLoopMode]; 
    currentMode = [[NSRunLoop currentRunLoop] currentMode]; 
    if ((currentMode != nil) && ! [currentMode isEqual:NSDefaultRunLoopMode]) { 
     [calculatedModes addObject:currentMode]; 
    } 
    self.modes = calculatedModes; 
    assert([self.modes count] > 0); 

    // Create new request that's a clone of the request we were initialised with, 
    // except that it has our 'recursive request flag' property set on it. 

    // ... 

    // Latch the thread we were called on, primarily for debugging purposes. 

    self.clientThread = [NSThread currentThread]; 

    // Once everything is ready to go, create a data task with the new request. 

    self.task = [[[self class] sharedDemux] dataTaskWithRequest:recursiveRequest delegate:self modes:self.modes]; 
    assert(self.task != nil); 

    [self.task resume]; 
} 

一些蘋果工程師有良好的幽默感。

令人興奮的吧?

有關詳細信息,請參閱full apple sample

問題與WKWebView不兼容,因爲NSURLProtocol不適用於它。詳情請參閱next question