Cordova源碼解析(二)- 自定義UserAgent
摘要:如果是0說明沒有模塊正在修改 UserAgent ,能夠成功獲取到鎖, gCurrentLockToken 遞增,標緻當前有模塊正在修改 UserAgent ,並回調 block ,返回 gCurrentLockToken。如果隊列 gPendingSetUserAgentBlocks 釋放完成,說明釋放鎖的調用次數>加鎖的次數,不做操作,然後把 gCurrentLockToken 置爲0。
本文轉載自: https://www.jianshu.com/p/0af14da1fbc6。 作者: 莫云溪。
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:(NSInt
加鎖
每次加鎖成功會返回一個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了
如果感覺這篇文章不錯可以點擊在看:point_down: