我们正在构建适用于 iOS 的浏览器。我们决定尝试使用自定义 NSURLProtocol 子类来实现我们自己的缓存方案并执行用户代理欺骗。它很好地完成了这两件事……问题是,导航到某些站点(msn.com 是最糟糕的站点)会导致整个应用程序 UI 卡住长达十五秒。显然有什么东西阻塞了主线程,但它不在我们的代码中。
此问题仅出现在 UIWebView 和自定义协议(protocol)的组合中。如果我们换入 WKWebView(由于各种原因我们不能使用),问题就会消失。同样,如果我们不注册协议(protocol)以使其永远不会被使用,问题就会消失。
协议(protocol)的作用似乎也无关紧要;我们编写了一个简单的虚拟协议(protocol),它除了转发响应外什么都不做(帖子底部)。我们将该协议(protocol)放入没有任何其他代码的基本测试浏览器中——结果相同。我们还尝试使用其他人的 (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)
}
}
最佳答案
我刚刚检查了 msn.com 的 NSURLProtocol 行为,发现在某些时候 startLoading 方法在 WebCoreSynchronousLoaderRunLoopMode 模式下被调用。这会导致主线程阻塞。
浏览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];
}
有些 Apple 工程师很有幽默感。
Exciting huh?
参见 full apple sample了解详情。
问题无法通过 WKWebView 重现,因为 NSURLProtocol 不适用于它。参见 next question了解详情。
关于ios - NSURLProtocol + UIWebView + 某些域 = app UI frozen,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31327785/
我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server
我知道我可以指定某些字段来使用pluck查询数据库。ids=Item.where('due_at但是我想知道,是否有一种方法可以指定我想避免从数据库查询的某些字段。某种反拔?posts=Post.where(published:true).do_not_lookup(:enormous_field) 最佳答案 Model#attribute_names应该返回列/属性数组。您可以排除其中一些并传递给pluck或select方法。像这样:posts=Post.where(published:true).select(Post.attr
这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下
我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的
如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:
1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里
我对如何计算通过{%assignvar=0%}赋值的变量加一完全感到困惑。这应该是最简单的任务。到目前为止,这是我尝试过的:{%assignamount=0%}{%forvariantinproduct.variants%}{%assignamount=amount+1%}{%endfor%}Amount:{{amount}}结果总是0。也许我忽略了一些明显的东西。也许有更好的方法。我想要存档的只是获取运行的迭代次数。 最佳答案 因为{{incrementamount}}将输出您的变量值并且不会影响{%assign%}定义的变量,我
print"Enteryourpassword:"pass=STDIN.noecho(&:gets)puts"Yourpasswordis#{pass}!"输出:Enteryourpassword:input.rb:2:in`':undefinedmethod`noecho'for#>(NoMethodError) 最佳答案 一开始require'io/console'后来的Ruby1.9.3 关于ruby-为什么不能使用类IO的实例方法noecho?,我们在StackOverflow上
我有一个数组数组,想将元素附加到子数组。+=做我想做的,但我想了解为什么push不做。我期望的行为(并与+=一起工作):b=Array.new(3,[])b[0]+=["apple"]b[1]+=["orange"]b[2]+=["frog"]b=>[["苹果"],["橙子"],["Frog"]]通过推送,我将推送的元素附加到每个子数组(为什么?):a=Array.new(3,[])a[0].push("apple")a[1].push("orange")a[2].push("frog")a=>[[“苹果”、“橙子”、“Frog”]、[“苹果”、“橙子”、“Frog”]、[“苹果”、“
我想编写一个ruby脚本来递归复制目录结构,但排除某些文件类型。因此,给定以下目录结构:folder1folder2file1.txtfile2.txtfile3.csfile4.htmlfolder2folder3file4.dll我想复制这个结构,但不包含.txt和.cs文件。因此,生成的目录结构应如下所示:folder1folder2file4.htmlfolder2folder3file4.dll 最佳答案 您可以使用查找模块。这是一个代码片段:require"find"ignored_extensions=[".cs"