本文分析的源码是基于Cordova 4.2.1版本,Cordova官网。
Cordova源码解析(二)- 自定义UserAgent
UIWebView没有提供设置UserAgent的接口,但是有一个办法可以间接的设置。
1 | NSDictionary* dict = [[NSDictionary alloc] initWithObjectsAndKeys:value, @"UserAgent", nil]; |
通过设置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文件中定义了四个方法
1 | // 获取UIWebView默认的UserAgent |
加锁
每次加锁成功会返回一个NSInteger类型的token,在释放锁的时候需要把token传入。token会不断递增,保证每次加锁返回的token都不回重复。加锁的实现代码如下:
1 | // CDVUserAgentUtil.m |
调用acquireLock:,首先会判断gCurrentLockToken是否等于0
- 如果是0说明没有模块正在修改
UserAgent,能够成功获取到锁,gCurrentLockToken递增,标致当前有模块正在修改UserAgent,并回调block,返回gCurrentLockToken - 如果不为0说明当前有模块正在修改
UserAgent,将block回调存在一个队列gPendingSetUserAgentBlocks中
释放锁
释放锁需要传入token,释放锁代码如下:
1 | + (void)releaseLock:(NSInteger*)lockToken |
- 如果要释放的
lockToken为0,说明还没加过锁,就调用释放了,直接返回 - 从队列
gPendingSetUserAgentBlocks中取出最早加入的block,从队列中移除 gCurrentLockToken递增生成新token,回调block- 如果队列
gPendingSetUserAgentBlocks释放完成,说明释放锁的调用次数>加锁的次数,不做操作,然后把gCurrentLockToken置为0
设置UserAgent
在Cordova实际运用中,操作锁的时机:
加锁时机:CDVViewController加载完毕,在viewDidLoad里调用
释放锁时机:
UIWebView的webViewDidFinishLoad:回调UIWebView的webView:didFailLoadWithError:回调CDVViewController的deallocCDVViewController的viewDidUnload
加锁代码,省略了不相关代码
1 | // CDVViewController.m |
释放锁代码,这里只看正常逻辑,在网页加载完成回调webViewDidFinishLoad:中释放逻辑。不考虑异常情况,省略了不相关代码。
1 | // CDVUIWebViewNavigationDelegate.m |
在webViewDidFinishLoad:回调时,UserAgent已经设置成功,所以可以释放锁,让其它WebView操作UserDefault了。