菜鸟教程小白 发表于 2022-12-13 08:58:35

ios - NSURLProtocol + UIWebView + 某些域 = 应用 UI 卡住


                                            <p><p>我们正在为 iOS 构建一个浏览器。我们决定尝试使用自定义的 <code>NSURLProtocol</code> 子类来实现我们自己的缓存方案并执行用户代理欺骗。它在这两件事上都做得很好......问题是,导航到某些网站(msn.com 是最糟糕的)将导致 <em>整个应用程序的</em> UI 卡住长达 15 秒。显然有些东西阻塞了主线程,但它不在我们的代码中。</p>

<p>此问题仅在 <code>UIWebView</code> 和自定义协议(protocol)的组合中出现。如果我们换入 <code>WKWebView</code> (由于各种原因我们不能使用它),问题就会消失。同样,如果我们不注册协议(protocol)以使其永远不会被使用,那么问题就会消失。</p>

<p>协议(protocol)的作用似乎也无关紧要<em></em>;我们编写了一个简单的虚拟协议(protocol),除了转发响应之外什么都不做(帖子底部)。我们将该协议(protocol)放入没有任何其他代码的准系统测试浏览器中——结果相同。我们还尝试使用其他人的 (<code>RNCachingURLProtocol</code>) 并观察到相同的结果。似乎这两个组件与某些页面的简单组合会导致卡住。我无法尝试解决(甚至调查)这个问题,并且非常感谢任何指导或提示。谢谢!</p>

<pre><code>import UIKit

private let KEY_REQUEST_HANDLED = &#34;REQUEST_HANDLED&#34;

final class CustomURLProtocol: NSURLProtocol {
    var connection: NSURLConnection!

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

    override class func canonicalRequestForRequest(request: NSURLRequest) -&gt; NSURLRequest {
      return request
    }

    override class func requestIsCacheEquivalent(aRequest: NSURLRequest, toRequest bRequest: NSURLRequest) -&gt; 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)
    }
}
</code></pre></p>
                                    <br><hr><h1><strong>Best Answer-推荐答案</ strong></h1><br>
                                            <p><p>我刚刚在 msn.com 上检查了 <code>NSURLProtocol</code> 的行为,发现在某个时候 <code>startLoading</code> 方法在 <code>WebCoreSynchronousLoaderRunLoopMode</code> 模式下被调用。这会导致主线程阻塞。</p>

<p>浏览<a href="https://developer.apple.com/library/ios/samplecode/CustomHTTPProtocol/Listings/CustomHTTPProtocol_Core_Code_CustomHTTPProtocol_m.html#//apple_ref/doc/uid/DTS40013653-CustomHTTPProtocol_Core_Code_CustomHTTPProtocol_m-DontLinkElementID_10" rel="noreferrer noopener nofollow">CustomHTTPProtocol Apple sample code</a> ,我找到了描述这个问题的评论。修复方式如下:</p>

<pre><code>@interface CustomHTTPProtocol () &lt;NSURLSessionDataDelegate&gt;

@property (atomic, strong, readwrite) NSThread * clientThread; ///&lt; 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&#39;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&#39;s not read before
*it&#39;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&#39;t call -startLoading twice

    // Calculate our effective run loop modes.In some circumstances (yes I&#39;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 &#34;WebCoreSynchronousLoaderRunLoopMode&#34;
    // but it&#39;s better not to hard-code that here.

    assert(self.modes == nil);
    calculatedModes = ;
    ;
    currentMode = [ currentMode];
    if ( (currentMode != nil) &amp;&amp; ! ) {
      ;
    }
    self.modes = calculatedModes;
    assert( &gt; 0);

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

    // ...

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

    self.clientThread = ;

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

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

    ;
}
</code></pre>

<p>一些 Apple 工程师很有幽默感。</p>

<blockquote>
<p>Exciting huh?</p>
</blockquote>

<p>见 <a href="https://developer.apple.com/library/ios/samplecode/CustomHTTPProtocol/Listings/CustomHTTPProtocol_Core_Code_CustomHTTPProtocol_m.html#//apple_ref/doc/uid/DTS40013653-CustomHTTPProtocol_Core_Code_CustomHTTPProtocol_m-DontLinkElementID_10" rel="noreferrer noopener nofollow">full apple sample</a>了解详情。</p>

<p><code>WKWebView</code> 无法重现问题,因为 <code>NSURLProtocol</code> 不适用于它。见 <a href="https://stackoverflow.com/questions/24208229/wkwebview-and-nsurlprotocol-not-working" rel="noreferrer noopener nofollow">next question</a>了解详情。</p></p>
                                   
                                                <p style="font-size: 20px;">关于ios - NSURLProtocol &#43; UIWebView &#43; 某些域 = 应用 UI 卡住,我们在Stack Overflow上找到一个类似的问题:
                                                        <a href="https://stackoverflow.com/questions/31327785/" rel="noreferrer noopener nofollow" style="color: red;">
                                                                https://stackoverflow.com/questions/31327785/
                                                        </a>
                                                </p>
                                       
页: [1]
查看完整版本: ios - NSURLProtocol &#43; UIWebView &#43; 某些域 = 应用 UI 卡住