UIwebView 没有提供设置 UserAgent 的接口, 但是有一个办法可以间接的设置.
- NSDictionary* dict = [[NSDictionary alloc] initWithObjectsAndKeys:value, @"UserAgent", nil];
- [[NSUserDefaults standardUserDefaults] registerDefaults:dict];
通过设置 NSUserDefaults 中 UserAgent 的值来修改, 但是这种设置方法有一个限制, 需要在 UIWebView 的 loadRequest 之前调用才能生效 (加载 PDF 比较特殊). 这是 Cordova 源码中关于这个问题的描述
Setting the UserAgent must occur before a UIWebView is instantiated.
It is read per instantiation, so it does not affect previously created views.
Except! When a PDF is loaded, all currently active UIWebViews reload their
User-Agent from the NSUserDefaults some time after the DidFinishLoad of the PDF bah!
CDVUserAgentUtil
在多 WebView 的情况下, 如果每个 WebView 都有不同的 UserAgent, 就会产生数据竞争的问题, 大家都要修改 NSUserDefaults 中 UserAgent 的值, 于是需要对资源加锁来保证每个 WebView 都设置预期的 UserAgent. 在 Cordova 中, 专门有一个类 CDVUserAgentUtil 来实现这个功能.
CDVUserAgentUtil.h 文件中定义了四个方法
- // 获取 UIWebView 默认的 UserAgent
- + (NSString*)originalUserAgent;
- // 获取锁
- + (void)acquireLock:(void (^)(NSInteger lockToken))block;
- // 释放锁
- + (void)releaseLock:(NSInteger*)lockToken;
- // 设置 UIWebView 的 UserAgent
- + (void)setUserAgent:(NSString*)value lockToken:(NSInteger)lockToken;
加锁
每次加锁成功会返回一个 NSInteger 类型的 token, 在释放锁的时候需要把 token 传入. token 会不断递增, 保证每次加锁返回的 token 都不回重复. 加锁的实现代码如下:
- // CDVUserAgentUtil.m
- + (void)acquireLock:(void (^)(NSInteger lockToken))block
- {
- if (gCurrentLockToken == 0) {
- gCurrentLockToken = ++gNextLockToken;
- VerboseLog(@"Gave lock %d", gCurrentLockToken);
- block(gCurrentLockToken);
- } else {
- if (gPendingSetUserAgentBlocks == nil) {
- gPendingSetUserAgentBlocks = [[NSMutableArray alloc] initWithCapacity:4];
- }
- VerboseLog(@"Waiting for lock");
- [gPendingSetUserAgentBlocks addObject:block];
- }
- }
调用 acquireLock:, 首先会判断 gCurrentLockToken 是否等于 0
如果是 0 说明没有模块正在修改 UserAgent, 能够成功获取到锁, gCurrentLockToken 递增, 标致当前有模块正在修改 UserAgent, 并回调 block, 返回 gCurrentLockToken
如果不为 0 说明当前有模块正在修改 UserAgent, 将 block 回调存在一个队列
gPendingSetUserAgentBlocks
中
释放锁
释放锁需要传入 token, 释放锁代码如下:
- + (void)releaseLock:(NSInteger*)lockToken
- {
- if (*lockToken == 0) {
- return;
- }
- NSAssert(gCurrentLockToken == *lockToken, @"Got token %ld, expected %ld", (long)*lockToken, (long)gCurrentLockToken);
- VerboseLog(@"Released lock %d", *lockToken);
- if ([gPendingSetUserAgentBlocks count]> 0) {
- void (^block)() = [gPendingSetUserAgentBlocks objectAtIndex:0];
- [gPendingSetUserAgentBlocks removeObjectAtIndex:0];
- gCurrentLockToken = ++gNextLockToken;
- NSLog(@"Gave lock %ld", (long)gCurrentLockToken);
- block(gCurrentLockToken);
- } else {
- gCurrentLockToken = 0;
- }
- *lockToken = 0;
- }
如果要释放的 lockToken 为 0, 说明还没加过锁, 就调用释放了, 直接返回
从队列
gPendingSetUserAgentBlocks
中取出最早加入的 block, 从队列中移除
gCurrentLockToken 递增生成新 token, 回调 block
如果队列
gPendingSetUserAgentBlocks
释放完成, 说明释放锁的调用次数 > 加锁的次数, 不做操作, 然后把 gCurrentLockToken 置为 0
设置 UserAgent
在 Cordova 实际运用中, 操作锁的时机:
加锁时机: CDVViewController 加载完毕, 在 viewDidLoad 里调用
释放锁时机:
UIWebView 的
webViewDidFinishLoad:
回调
UIWebView 的
webView:didFailLoadWithError:
回调
CDVViewController 的 dealloc
CDVViewController 的 viewDidUnload
加锁代码, 省略了不相关代码
- // CDVViewController.m
- - (void)viewDidLoad
- {
- [CDVUserAgentUtil acquireLock:^(NSInteger lockToken) {
- _userAgentLockToken = lockToken;
- [CDVUserAgentUtil setUserAgent:self.userAgent lockToken:lockToken];
- NSURLRequest* appReq = [NSURLRequest requestWithURL:appURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0];
- [self.webViewEngine loadRequest:appReq];
- }];
- }
释放锁代码, 这里只看正常逻辑, 在网页加载完成回调
webViewDidFinishLoad:
中释放逻辑. 不考虑异常情况, 省略了不相关代码.
- // CDVUIWebViewNavigationDelegate.m
- - (void)webViewDidFinishLoad:(UIWebView*)theWebView
- {
- NSLog(@"Finished load of: %@", theWebView.request.URL);
- CDVViewController* vc = (CDVViewController*)self.enginePlugin.viewController;
- // It's safe to release the lock even if this is just a sub-frame that's finished loading.
- [CDVUserAgentUtil releaseLock:vc.userAgentLockToken];
- }
在
webViewDidFinishLoad:
回调时, UserAgent 已经设置成功, 所以可以释放锁, 让其它 WebView 操作 UserDefault 了
来源: http://www.jianshu.com/p/0af14da1fbc6