[特斯拉組件]iOS 高性能 PageController無標(biāo)題文章

1.組件介紹

Page是企鵝FM研發(fā)的分頁組件卓舵,包括支持分頁非交互切換(通過方法調(diào)用導(dǎo)航切換)和交互切換(屏幕的手勢滑動(dòng)),多個(gè)分頁Controller和View的管理囱修。

1.1需求背景

為什么棄用UIPageViewController,首先介紹一下UIPageViewController,這是系統(tǒng)為開發(fā)者定制的分頁組件外傅,提供了兩種分頁切換的效果羊瘩,一是滑動(dòng) 二是翻頁。且提供了前后切換的回調(diào)柒莉。

a) UIPageViewController在iOS8以下的系統(tǒng)運(yùn)行是有問題的闻坚,可以參考stackFlow上的癥狀描述https://stackoverflow.com/questions/12939280/uipageviewcontroller-navigates-to-wrong-page-with-scroll-transition-style/12939384#12939384

This is actually a bug in UIPageViewController. It occurs only with

the scroll style (.Scroll) and only after calling

setViewControllers:direction:animated:completion: with animated:YES.

Thus there are two workarounds:

Don’t use UIPageViewControllerTransitionStyleScroll.

Or, if you call setViewControllers:direction:animated:completion:, use animated:NO.

To see the bug clearly, call

setViewControllers:direction:animated:completion: and then, in the

interface (as user), navigate left (back) to the preceding page

manually. You will navigate back to the wrong page: not the preceding

page at all, but the page you were on when

setViewControllers:direction:animated:completion: was called.

The reason for the bug appears to be that, when using the scroll

style, UIPageViewController does some sort of internal caching. Thus,

after the call to setViewControllers:direction:animated:completion:, it

fails to clear its internal cache. It thinks it knows what the preceding

page is. Thus, when the user navigates leftward to the preceding page,

UIPageViewController fails to call the dataSource method

pageViewController:viewControllerBeforeViewController:, or calls it with

the wrong current view controller.

大意是說使用.Scroll的時(shí)候,UIPageViewController做了內(nèi)部緩存的排序兢孝,當(dāng)調(diào)用

setViewControllers:direction:animated:completion:

時(shí) 它認(rèn)為自己知道了前一個(gè)的分頁存在窿凤,當(dāng)調(diào)用前一個(gè)頁面的時(shí)候,就不會(huì)去調(diào)用dataSource的方法跨蟹。

b)

UIPageViewController的DataSource和Delegate的接口過于簡單雳殊,對于比較復(fù)雜的情況(比如除了分頁以外還有其他View的情況下)無法處理。參照下面的例圖窗轩,我有一個(gè)tab下面有小黃條夯秃,跟著手勢橫向滑動(dòng)的同時(shí)也橫向滑動(dòng),這里系統(tǒng)的UIPageViewController無法支持品姓。其外寝并,我還需要子頁面縱向滑動(dòng)時(shí)候去修改Cover和Tab的frame。所以UIPageViewController無法滿足比較復(fù)雜的需求腹备。

c) 低配的機(jī)器會(huì)產(chǎn)生卡頓問題衬潦,因?yàn)橄到y(tǒng)的UIPageViewController,在快速切換的時(shí)候,會(huì)釋放掉不用的頁面植酥,所以在快速回切的時(shí)候會(huì)造成卡頓镀岛,可以參考下面的性能測試。

綜上所述友驮,棄用了系統(tǒng)的UIPageViewController漂羊。

1.2使用說明

使用非常簡單,繼承組件的類卸留,實(shí)現(xiàn)相應(yīng)的delegate和datasourc就可以了走越。

Page的例圖如下:

頁面層次關(guān)系如下:

圖中由一個(gè)圖片,3個(gè)欄目 (詳情耻瑟,節(jié)目旨指,評論)和一個(gè)List組成赏酥。可以分為三個(gè)層次谆构,Cover,Tab和Page裸扶。

Page組件層次關(guān)系如下,

圖中的ShowListController是節(jié)目分頁搬素,AlbumListController是專輯分頁.

2.組件架構(gòu)設(shè)計(jì)

2.1 架構(gòu)介紹

類圖如下:

簡要說明下各個(gè)協(xié)議的作用:

FMPageDataSource,? 提供子頁面呵晨,子頁面的個(gè)數(shù),子頁面展示的frame給PageController熬尺。

FMPageDelegate,? 提供頁面交互切換和非交互切換的回調(diào)給上層以及頁面的縱向滑動(dòng)和橫向滑動(dòng)的contentoffset給上層摸屠。

FMTabDataSource,? 提供TabView的具體展示效果。

FMTabDelegate,? 提供TabView的點(diǎn)擊響應(yīng)給上層猪杭。

FMCoverController,? 提供CoverView給CoverController.

其中餐塘,F(xiàn)MTabController默認(rèn)遵循FMTabDataSource,FMTabDelegateSource,FMPageDataSource,FMPageDelegate協(xié)議。FMCoverController遵循FMCoverDatasource協(xié)議皂吮。

2.2 接口設(shè)計(jì)

接口遵循高內(nèi)聚和低耦合的特性戒傻,只把Delegate和DataSource開放給上層,同時(shí)做接口分離蜂筹,把Page,Tab,Cover特性的分離需纳。 代碼如下:

@interfaceFMTabController : FMBusinessViewController @interfaceFMCoverController : FMTabController

2.3 Child頁面的生命周期管理和切換。

1.UIScrollView支持分頁效果艺挪,手勢處理及交互操作多個(gè)回調(diào)方法可以實(shí)現(xiàn)頁面的切換效果不翩。

2.生命周期管理有兩種方式 a.頻繁地add/remove ChildController b.使用下面的代碼實(shí)現(xiàn)生命周期的管理:

1)shouldAutomaticallyForwardAppearanceMethods2)beginAppearanceTransition: animated:3)endAppearanceTransition

a.會(huì)產(chǎn)生一個(gè)重大缺陷,就是頻繁切換的卡頓問題麻裳。

b.不需要頻繁地去調(diào)用add/remove,1)方法避免了 add/remove產(chǎn)生的生命周期口蝠,2)和3)保證了開發(fā)者可以自己控制ChildController的生命周期。

Page的生命周期圖如下:

初次或者reloadPage

交互切換和非交互切換

2.4 性能問題擴(kuò)展

以下通過Iphone5 模擬器 10.3系統(tǒng)津坑,與UIPageViewController做了性能上的對比妙蔗。

UIPageViewController 快速切換內(nèi)存占用情況

UIPageViewController 快速切換GPU占用情況

Page組件快速切換內(nèi)存占用情況

Page組件快速切換GPU占用情況

從上圖中內(nèi)存占用圖標(biāo)的波動(dòng)情況可以看出UIPageViewController在快速切換的時(shí),會(huì)盡可能快地釋放掉不用的controller及其view(主要是view)以保證內(nèi)存占用較小疆瑰,所以圖標(biāo)指標(biāo)先才會(huì)頻繁的波動(dòng),與UIPageViewController作對比眉反,Page組件用空間換時(shí)間的策略避免頁面卡頓。

3.技術(shù)實(shí)現(xiàn)的難點(diǎn)

從技術(shù)上看穆役,可以分為以下四個(gè)點(diǎn):

3.1 接口的設(shè)計(jì)寸五。

接口的設(shè)計(jì),是整個(gè)架構(gòu)的核心耿币,如果開始設(shè)計(jì)不好梳杏,會(huì)導(dǎo)致后續(xù)的擴(kuò)展就是加屬性和加方法,導(dǎo)致代碼越來越龐大,以致無法維護(hù)秘狞,所以盡量保證簡潔叭莫,職能單一,可擴(kuò)展烁试。

起初為了讓delegate和datasource可以從Controller分離出去,把delegate和datasource都暴露了出去拢肆,但這樣相當(dāng)于多了5個(gè)屬性减响,對于上層來說并不便于理解這些接口,仿照UITableViewController郭怪,由繼承的方式實(shí)現(xiàn)這些協(xié)議支示,讓接口更加簡潔。

3.2 頁面縱向滑動(dòng)跟隨Tab和Cover一起滑動(dòng)鄙才。

通過上面的動(dòng)態(tài)圖颂鸿,可以知道,Page組件有這樣一個(gè)功能,子頁面縱向滑動(dòng)會(huì)跟隨Tab和Cover一起向上滑動(dòng)攒庵,其中cover的滑動(dòng)的實(shí)現(xiàn)是監(jiān)聽ChildController的ScrollView的contentOffset嘴纺,修改Tab的height或y。Scrollview的滑動(dòng)有一個(gè)難點(diǎn)浓冒,怎樣保證ScrollView的向下滑動(dòng)的反彈處緊貼Tab栽渴,而Scrollview又可以向上滑動(dòng)到導(dǎo)航欄。

首先Scrollview的可見范圍是整屏的稳懒,也就是設(shè)置frame為整屏闲擦,Scrollview滑動(dòng)的范圍,就由ContentInset,ContentOffset

共同決定场梆。因?yàn)槲覀冎繳IScrollView的滑動(dòng)范圍會(huì)緊貼scrollView的bounds墅冷。所以首先,修改ContentInset的Top為-tabH-tabY或油,可以保證向下滑動(dòng)到Tab的下邊緣處反彈寞忿,又由于frame是整屏的,向上滑動(dòng)時(shí)候就可以滑動(dòng)導(dǎo)航欄装哆,代碼如下:

scrollView.contentInset =? UIEdgeInsetsMake([self.dataSource pageTop], contentInset.left, contentInset.bottom, contentInset.right);scrollView.frame = CGRectMake(0,0,Screen_Width,Screen_Height)

其中的pageTop就是tab的下邊緣處罐脊。

3.3 不相鄰頁面切換的問題

不相鄰頁面的非交互切換會(huì)閃過中間的頁面,產(chǎn)生不好的用戶體驗(yàn)蜕琴,本組件的解決方法是

非交互切換萍桌,模擬切換的動(dòng)畫,這里需要考慮的一個(gè)復(fù)雜情況是第一次動(dòng)畫還未結(jié)束就開始第二次凌简,這時(shí)候需要提前結(jié)束第一次動(dòng)畫上炎。修改后的效果圖如下,

3.4 平衡性能的問題。

因?yàn)镻age要管理多個(gè)controller和view藕施,如果子頁面到1000寇损,甚至10000個(gè)怎樣去處理。比如微信閱讀的一本書就可能有10000頁裳食。所以這里如果全部都保存就可能產(chǎn)生一個(gè)問題矛市,內(nèi)存會(huì)不會(huì)過大。

觀察UIPageViewController,它到一定的內(nèi)存限制诲祸,會(huì)主動(dòng)去釋放很久沒翻過的頁面浊吏。所以這里,可以使用LRUCache的機(jī)制救氯,只保存一定數(shù)量的頁面找田。由于本應(yīng)用并不涉及到過多的子頁面,考慮的時(shí)間花銷和內(nèi)存着憨,全部保存了所有頁面墩衙。

demo地址:https://github.com/xichen744/SPPage

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市甲抖,隨后出現(xiàn)的幾起案子漆改,更是在濱河造成了極大的恐慌,老刑警劉巖惧眠,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件籽懦,死亡現(xiàn)場離奇詭異,居然都是意外死亡氛魁,警方通過查閱死者的電腦和手機(jī)暮顺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秀存,“玉大人捶码,你說我怎么就攤上這事』蛄矗” “怎么了惫恼?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長澳盐。 經(jīng)常有香客問我祈纯,道長,這世上最難降的妖魔是什么叼耙? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任腕窥,我火速辦了婚禮,結(jié)果婚禮上筛婉,老公的妹妹穿的比我還像新娘簇爆。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布入蛆。 她就那樣靜靜地躺著响蓉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪哨毁。 梳的紋絲不亂的頭發(fā)上枫甲,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天,我揣著相機(jī)與錄音扼褪,去河邊找鬼言秸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛迎捺,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播查排,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼凳枝,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了跋核?” 一聲冷哼從身側(cè)響起岖瑰,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎砂代,沒想到半個(gè)月后蹋订,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡刻伊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年露戒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捶箱。...
    茶點(diǎn)故事閱讀 40,015評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡智什,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出丁屎,到底是詐尸還是另有隱情荠锭,我是刑警寧澤,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布晨川,位于F島的核電站证九,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏共虑。R本人自食惡果不足惜愧怜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望看蚜。 院中可真熱鬧叫搁,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至惨奕,卻和暖如春雪位,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背梨撞。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工雹洗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人卧波。 一個(gè)月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓时肿,卻偏偏與公主長得像,于是被迫代替她去往敵國和親港粱。 傳聞我的和親對象是個(gè)殘疾皇子螃成,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評論 2 355

推薦閱讀更多精彩內(nèi)容