【问题标题】:UIWebViewDelegate not monitoring XMLHttpRequest?UIWebViewDelegate 不监控 XMLHttpRequest?
【发布时间】:2011-07-18 05:15:26
【问题描述】:

UIWebViewDelegate 确实不监视使用 XMLHttpRequest 发出的请求吗?如果是这样,有没有办法监控这类请求?

例如UIWebViewDelegate 在-(BOOL) webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSMutableURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType 中没有捕捉到这一点;

var xhr = new XMLHttpRequest();
xhr.open("GET", "http://www.google.com", true);

xhr.onreadystatechange=function() 
{
    if (xhr.readyState==4) 
    {
        alert(xhr.responseText);
    }
}

xhr.send();

【问题讨论】:

    标签: ajax xcode uiwebview xmlhttprequest


    【解决方案1】:

    有趣的问题。

    有两个部分可以完成这项工作:JavaScript 处理程序和 UIWebView 委托方法。在 JavaScript 中,我们可以修改原型方法以在创建 AJAX 请求时触发事件。使用我们的 UIWebView 委托,我们可以捕获这些事件。

    JavaScript 处理程序

    我们需要在发出 AJAX 请求时得到通知。我找到了解决方案here

    在我们的例子中,为了使代码正常工作,我将以下 JavaScript 放入了一个名为 ajax_handler.js 的资源中,该资源与我的应用程序捆绑在一起。

    var s_ajaxListener = new Object();
    s_ajaxListener.tempOpen = XMLHttpRequest.prototype.open;
    s_ajaxListener.tempSend = XMLHttpRequest.prototype.send;
    s_ajaxListener.callback = function () {
        window.location='mpAjaxHandler://' + this.url;
    };
    
    XMLHttpRequest.prototype.open = function(a,b) {
      if (!a) var a='';
      if (!b) var b='';
      s_ajaxListener.tempOpen.apply(this, arguments);
      s_ajaxListener.method = a;  
      s_ajaxListener.url = b;
      if (a.toLowerCase() == 'get') {
        s_ajaxListener.data = b.split('?');
        s_ajaxListener.data = s_ajaxListener.data[1];
      }
    }
    
    XMLHttpRequest.prototype.send = function(a,b) {
      if (!a) var a='';
      if (!b) var b='';
      s_ajaxListener.tempSend.apply(this, arguments);
      if(s_ajaxListener.method.toLowerCase() == 'post')s_ajaxListener.data = a;
      s_ajaxListener.callback();
    }
    

    这实际上将把浏览器的位置更改为一些组成的 URL 方案(在本例中,mpAjaxHandle),其中包含有关所发出请求的信息。不用担心,我们的代表抓住了这个,位置不会改变。

    UIWebView 委托

    首先,我们需要读取我们的 JavaScript 文件。我建议将其存储在静态变量中。我有使用 +initialize 的习惯。

    static NSString *JSHandler;
    
    + (void)initialize {
        JSHandler = [[NSString stringWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"ajax_handler" withExtension:@"js"] encoding:NSUTF8StringEncoding error:nil] retain];
    }
    

    接下来,我们想在页面加载完成之前注入这个 JavaScript,这样我们就可以接收所有事件。

    - (void)webViewDidStartLoad:(UIWebView *)webView {
        [webView stringByEvaluatingJavaScriptFromString:JSHandler];
    }
    

    最后,我们要捕获事件。

    既然 URL Scheme 是组成的,我们不想真正遵循它。我们返回 NO,一切都很好。

    #define CocoaJSHandler          @"mpAjaxHandler"
    
    - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
        if ([[[request URL] scheme] isEqual:CocoaJSHandler]) {
            NSString *requestedURLString = [[[request URL] absoluteString] substringFromIndex:[CocoaJSHandler length] + 3];
    
            NSLog(@"ajax request: %@", requestedURLString);
            return NO;
        }
    
        return YES;
    }
    

    我使用该解决方案创建了一个示例项目,但无处托管它。如果你能主持,你可以给我发消息,我会相应地编辑这篇文章。

    【讨论】:

    • 请注意 XCode 不会自动将扩展名为 .js 的文件添加到包中。您需要手动添加文件。
    • 嗨,迈克 - 有没有机会与我分享示例项目?我真的很感激
    • 请注意,我只是将 JS 示例直接打入一个 NSString 进行测试,起初它并没有工作。我不得不添加一个;在 XMLHttpRequest.prototype.send/open 分配的尾随 } 之后。
    • 请注意,我在整个过程中添加了一个 if(!s_ajaxListener),即使 JS 运行/评估了两次,它也能继续工作。
    【解决方案2】:

    您可以使用 NSURLProtocol。例如,如果您使用http://localhost/path 调用 XMLHttpRequest,您可以使用以下方式处理它:

    @interface YourProtocol: NSURLProtocol
    

    然后为实现:

    + (BOOL)canInitWithRequest:(NSURLRequest *)request 
    {
        return [request.URL.host isEqualToString:@"localhost"];
    }
    
    + (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request
    {
        return request;
    }
    
    - (void) startLoading
    {
        // Here you handle self.request 
    }
    
    - (void)stopLoading
    {
    }
    

    您需要按如下方式注册协议:

        [NSURLProtocol registerClass:[YourProtocol class]];
    

    【讨论】:

    • 这里的问题是你失去了对 UIWebView 的所有和任何引用,除非你保留一个列表,即使那样,这也不是 100% 可靠
    • 实际上,与其他解决方案相比,这是一个更健康的解决方案(这非常hackish)。如果需要,您可以在某处对您的 UIWebView 实例进行类引用...此方法的功能也更强大(如果您想要的不仅仅是监控)
    • 值得一提的是,您需要调用 [self.client URLProtocolDidFinishLoading:self];在 startLoading 中完成请求。 (否则它将在 javascript 结束时超时。)
    • 更正确的是,您需要调用类似 [self.client URLProtocol:self didReceiveResponse:[NSURLResponse new] cacheStoragePolicy:NSURLCacheStorageNotAllowed];然后 [self.client URLProtocolDidFinishLoading:self];在 startLoading 中完成请求。
    【解决方案3】:

    通过实现和注册 NSURLProtocol 的子类,您可以捕获来自 UIWebView 的所有请求。在某些情况下这可能有点矫枉过正,但如果您无法修改实际运行的 javascript,那么这是您最好的选择。

    在我的情况下,我需要捕获所有请求并为每个请求插入一个特定的 HTTP 标头。 我通过实现 NSURLProtocol,使用 registerClass 注册它并在我的子类中对 + (BOOL)canInitWithRequest:(NSURLRequest *)request 回答“是”来完成此操作,如果请求对应于我感兴趣的 URL。 然后你必须实现协议的其他方法,这可以通过使用 NSURLConnection 来完成,将协议类设置为委托并将 NSURLConnection 的委托方法重定向到 NSURLProtocolClient

    【讨论】:

    • 特别是如果你想要的只是监控请求,(不修改它)。这很容易: 1) 创建 NSURLProtocol 的子类 2) 实现 + (BOOL)canInitWithRequest:(NSURLRequest *)request { return NO;} 3) 使用 [NSURLProtocol registerClass:lShareSwymURLProtocol] 注册您的协议;在步骤 2) 的实现中,您可以在检查请求后添加所需的任何代码。如果您希望标准加载机制继续,请确保返回 NO
    【解决方案4】:

    这似乎是真的。除了 UIWebViewDelegate 提供的功能之外,没有其他方法可以监控 UIWebView 正在做什么,除非您可能想出一种方法来使用 stringByEvaluatingJavaScriptFromString: 注入一些 Javascript 来完成您需要的工作。

    【讨论】:

      【解决方案5】:

      正如其他人提到的,UIWebViewDelegate 只观察 window.location 和 iframe.src 的变化。

      如果您只想使用 iOS 的自定义 url 方案, 你可以像这样利用<iframe>

      例如,如果你想像这样调用 URL Scheme

      objc://my_action?arg1=1&arg2=2
      

      在你的 js 文件中:

      /**
      * @param msg : path of your query
      * @param data : arguments list of your query
      */
      var call_url = function(msg, data){
          const scheme = "objc";
          var url = scheme + '://' + msg;
          if(data){
              var pstr = [];
              for(var k in data)
                  if(typeof data[k] != 'function')
                      pstr.push(encodeURIComponent(k)+"="+encodeURIComponent(data[k]));
              url += '?'+pstr.join('&');
          }
          var i = document.createElement("iframe");
          i.src = url;
          i.style.opacity=0;
          document.body.appendChild(i);
          setTimeout(function(){i.parentNode.removeChild(i)},200);
      }
      
      //when you call this custom url scheme
      call_url ("my_action", {arg1:1,arg2:2});
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-07-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-08-04
        • 2010-11-16
        • 1970-01-01
        相关资源
        最近更新 更多