摘要:如果是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];

通過設置 NSUserDefaultsUserAgent 的值來修改,但是這種設置方法有一個限制,需要在 UIWebViewloadRequest 之前調用才能生效(加載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 ,就會產生數據競爭的問題,大家都要修改 NSUserDefaultsUserAgent 的值,於是需要對資源加鎖來保證每個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 裏調用

釋放鎖時機:

  • UIWebViewwebViewDidFinishLoad: 回調

  • UIWebViewwebView:didFailLoadWithError: 回調

  • CDVViewControllerdealloc

  • CDVViewControllerviewDidUnload

加鎖代碼,省略了不相關代碼

// 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:

相關文章