create at 2016.11.07 20:58
背景
iOS的一個(gè)坑青扔。在線上的版本中锯七,iOS10系統(tǒng)中链快,app內(nèi)使用WKWebView當(dāng)作一個(gè)普通的子View來展示一個(gè)較長(zhǎng)的Web內(nèi)容組成一個(gè)hybrid頁面時(shí),會(huì)發(fā)生白屏的眉尸。經(jīng)過原生端的開發(fā)的排除域蜗,確認(rèn)是WKWebView的機(jī)制問題,并不是頁面加載不完整或者是被劫持而導(dǎo)致的問題噪猾。
為了更嚴(yán)謹(jǐn)?shù)呐懦鰡栴}所在霉祸,我拉去了原聲端的代碼再次確認(rèn)代碼邏輯是否存在導(dǎo)致該問題所在的bug。因?yàn)樵擁撁媸且粋€(gè)自定義的UITableView袱蜡,WKWebView只是UITableView的一個(gè)Cell里面的子View丝蹭,而且和UITableView的model層,也有很多的業(yè)務(wù)邏輯坪蚁,看起來比較費(fèi)勁奔穿。經(jīng)過了幾輪的調(diào)試,知識(shí)找到了一個(gè)導(dǎo)致導(dǎo)致死循環(huán)的一個(gè)調(diào)用敏晤,那邊的開發(fā)使用了RAC綁定WKWebView內(nèi)嵌UIScrollView的contentSize巫橄,去刷新UITableView,UITableView的回調(diào)獲取cell的高度的時(shí)候會(huì)導(dǎo)致循環(huán)調(diào)用茵典,一直的刷新UITableView獲取cell的高度湘换,除了會(huì)消耗性能,并沒有看出邏輯有太大問題。因?yàn)椴室校壳安⒉粫?huì)導(dǎo)致頁面出現(xiàn)一些莫名其妙的問題筹我,也不知道原來寫這部分代碼邏輯的同事初衷是什么,所以并沒有改動(dòng)這部分代碼帆离。另外蔬蕊,用了Charles看了一下這個(gè)頁面的請(qǐng)求,并不是頁面劫持導(dǎo)致的問題哥谷。
- 不是請(qǐng)求劫持導(dǎo)致的問題
- http請(qǐng)求完整
- 問題必現(xiàn)岸夯,證明是通用性問題
嘗試設(shè)置WKWebView的frame比contentSize小,在滾動(dòng)WKWebView的時(shí)候们妥,里面的內(nèi)容是可以全部展示的猜扮,并沒有出現(xiàn)白屏的問題〖嗌簦可以得出的結(jié)論是:WKWebView作為一個(gè)元素放在UITabViewCell里面旅赢,是沒問題問題的(當(dāng)然,性能問題在討論范圍)惑惶。
調(diào)試了大半天煮盼,并沒有找到問題的根源。于是先建立一個(gè)demo工程带污,先確認(rèn)和排出一些問題僵控。
- UITableViewCell中嵌套WKWebView是否會(huì)導(dǎo)致刷新問題
- UITabView中計(jì)算獲取嵌套了WKWebView的UITabViewCell計(jì)算高度是否準(zhǔn)確
建立工程,在UITableVie的一個(gè)UITableViewCell里面嵌套了一個(gè)WKWebView來重現(xiàn)工程中的情況鱼冀。
Reveal
先通過Reveal工具來看一下WKWebView的樹喉祭,先大概了解一些WKWebView的結(jié)構(gòu)。
WKScrollView
WKScrollView繼承于UIScrollView雷绢,在初始化的時(shí)將初始化一個(gè)WKScrollViewDegelageForwarder代理實(shí)例
下圖是WKScrollView的delegate的setter方法泛烙,可以清晰的看到各個(gè)delegate的類型
在WKScrollViewDegelageForwarder的實(shí)現(xiàn)中,明確的看到翘紊,WKScrollView的delegate(externalDelegate實(shí)例)的消息都通過message_forward的形式轉(zhuǎn)發(fā)到WKWebView(internalDelegate)實(shí)例中蔽氨。
下圖是WKScrollViewDegelageForwarder類的轉(zhuǎn)發(fā)實(shí)現(xiàn)
WKContentView
WKContentView就是WKWebView內(nèi)容渲染的容器。在Reveal的樹狀圖上面可以看到帆疟,渲染頁面中鹉究,展示在頁面上的渲染單元是WKCompositingView,WKCompositingView可以嵌套WKCompositingView踪宠。其中的一個(gè)WKCompositingView實(shí)例自赔,將包含多個(gè)WKCompositingView子實(shí)例。類似于UITableView的重用機(jī)制柳琢,多個(gè)WKCompositingView的父View就相當(dāng)于UITableView绍妨,WKCompositingView就相當(dāng)于UITableViewCell润脸,只展示可視區(qū)域的內(nèi)容,達(dá)到性能優(yōu)化的目的他去。
從下圖可以看到毙驯,一個(gè)WKWebView加載的web內(nèi)容,切割成多個(gè)WKCompositingView灾测,單個(gè)WKCompositingView重用單元的面積是375x512點(diǎn)爆价。
WKWebView
初始化
在WKWebView初始化的代碼中,可以看到這樣的一段初始化代碼
ScrollView回調(diào)
在WKWebView中媳搪,ScrollView相關(guān)的回調(diào)的調(diào)用鏈都是這樣的一個(gè)調(diào)用關(guān)系:
scrollview delegate's callback
->[WKWebView _updateVisibleContentRectAfterScrollInView:]
->[WKWebView _updateContentRectsWithState:]
->[WKContentView didUpdateVisibleRect:visibleRectInContentCoordinates
unobscuredRect:unobscuredRectInContentCoordinates
unobscuredRectInScrollViewCoordinates:unobscuredRect
obscuredInset:CGSizeMake(_obscuredInsets.left, _obscuredInsets.top)
scale:scaleFactor
minimumScale:[_scrollView minimumZoomScale]
inStableState:inStableState
isChangingObscuredInsetsInteractively:_isChangingObscuredInsetsInteractively
enclosedInScrollableAncestorView:scrollViewCanScroll([self _scroller])];
從調(diào)用鏈上清晰可以看到铭段,當(dāng)WKScrollView滾動(dòng)的時(shí)候,WKScrollView滾動(dòng)相關(guān)回調(diào)的消息秦爆,將會(huì)發(fā)送到WKWebView內(nèi)序愚,WKWebView實(shí)例內(nèi)scrollView的的回調(diào)將會(huì)調(diào)用WKContntView的刷新方法,刷新需要渲染的web內(nèi)容鲜结。
猜想
當(dāng)了解到WKWebView內(nèi)容的刷新機(jī)制以后,就可以合理的進(jìn)行猜想了活逆。
因?yàn)閃KWebView作為一個(gè)普通的UIView添加在UITableViewCell的contentView上精刷,因?yàn)轫?xiàng)目中UITableView和WKWebView的ScrollView都是豎向滾動(dòng)的,這兩個(gè)手勢(shì)動(dòng)作將會(huì)沖突蔗候,WKWebView只是一個(gè)子View怒允,需要通過設(shè)置內(nèi)置ScrollView的滾動(dòng)屬性來將WKWebView的滾動(dòng)功能關(guān)閉,保證父View--UITableView滾動(dòng)功能的正常使用锈遥。
因?yàn)閃KWebView使用過綁定內(nèi)置ScrollView的滾動(dòng)回調(diào)來刷新WKContentView內(nèi)需要渲染的web內(nèi)容的纫事,因?yàn)閃KWebView已經(jīng)被設(shè)定為禁止?jié)L動(dòng),自然不會(huì)再刷新需要渲染當(dāng)初在不在可視區(qū)域的內(nèi)容了所灸。因?yàn)閁ITableView的滾動(dòng)回調(diào)并沒有和WKWebView的內(nèi)的滾動(dòng)是綁定關(guān)系丽惶,所以在UITableView滾動(dòng)的時(shí)候,并不會(huì)觸發(fā)WKWebView的刷新爬立。這就是為什么在進(jìn)入頁面的時(shí)候钾唬,上面一部分內(nèi)容可以正常顯示,二下半部分顯示白屏的原因侠驯。當(dāng)然抡秆,在目前來說只是一個(gè)猜想。
驗(yàn)證
前面的猜想吟策,在經(jīng)過對(duì)源代碼的閱讀儒士,理論上是說得通的。現(xiàn)在就通過demo的代碼驗(yàn)證檩坚。有了上面的原理着撩,那么UITablbeView滾動(dòng)的時(shí)候诅福,觸發(fā)WKWebView刷新頁面即可?可知的是睹酌,WKWebView是調(diào)用_updateVisibleContentRectAfterScrollInView:方法來對(duì)WKContentView來刷新內(nèi)容的权谁。
由下圖可知,而WKWebView的_updateVisibleContentRects方法實(shí)現(xiàn)憋沿,也只是調(diào)用了_updateVisibleContentRectAfterScrollInView:旺芽,也就是說直接調(diào)用WKWebView實(shí)例的_updateVisibleContentRects就可以刷新了。
下面辐啄,用RAC來監(jiān)聽一下UITableView實(shí)例的contentOffset屬性采章,在contentOffset發(fā)生變化的時(shí)候,也就是UITableView實(shí)例滾動(dòng)的時(shí)候壶辜,就去調(diào)用一下WKWebView實(shí)例的_updateVisibleContentRects方法去刷新需要渲染的內(nèi)容悯舟。
@weakify(self);
[RACObserve(self.tableView, contentOffset) subscribeNext:^(id x) {
@strongify(self);
if ([self.webView respondsToSelector:@selector(_updateVisibleContentRects)]) {
((void(*)(id,SEL,BOOL))objc_msgSend)(self.webView,@selector(_updateVisibleContentRects),NO);
}
}];
在運(yùn)行demo工程的時(shí)候,結(jié)果按照猜想的發(fā)生了砸民,滾動(dòng)到WKWebView下方時(shí)抵怎,原來會(huì)白屏的區(qū)域正常的渲染內(nèi)容了。
解決方案
上方猜想被證實(shí)了岭参,那么說這個(gè)方案時(shí)可以行的反惕,而且對(duì)源代碼的理解并沒有太大的偏差。按照原理演侯,可以使用一下幾個(gè)方法來解決白屏的問題
- 用KVO方法監(jiān)聽UITableView的contnetOffset屬性姿染,contentOffset發(fā)生變化也就是說UITableView發(fā)生滾動(dòng),調(diào)用WKWebView實(shí)例的_updateVisibleContentRects秒际,刷新需要渲染的內(nèi)容
- UITableView是繼承自UIScrollView的悬赏,在代碼中實(shí)現(xiàn)UIScrollView的delegate,在delegate實(shí)現(xiàn)中手動(dòng)調(diào)用WKWebView實(shí)例等UIScrollViewDelegate的方法娄徊,原理和第一種方法一樣
- 使用CADisplayLink類闽颇,在CADisplayLink的回調(diào)方法里面調(diào)用WKWebView實(shí)例的_updateVisibleContentRects即可
上面三種方法的其實(shí)都是大同小異的,只是適合不同的場(chǎng)景寄锐。優(yōu)劣也不用說了进萄,一眼就能看出來了。