UIScrollview重用以及UIImage的坑

UITableView中的UITableViewCell是可以重用的。

什么是重用耍属,字面意思就是把內(nèi)容換一下寸宵,大體結(jié)構(gòu)不發(fā)生變化的二次使用檩帐。打個(gè)比方就是家里做飯术幔,一般都是吃完飯把餐具洗一下,下次再用湃密,而不是吃完就扔诅挑,下次再買(mǎi)新的(一次性除外)四敞。

所以重用的好處就是節(jié)省,較少每次創(chuàng)建或銷(xiāo)毀對(duì)象的開(kāi)銷(xiāo)拔妥,直觀的感受就是你的TableView滑起來(lái)更流暢了忿危,把成千上萬(wàn)條數(shù)據(jù)加載進(jìn)去,內(nèi)存也不會(huì)劇增没龙,app也就不不容易崩了铺厨。

以上都是我看文檔或者別的大牛說(shuō)的,自己沒(méi)試驗(yàn)過(guò)硬纤,親身感受也不真切解滓,于是乎我打算自己嘗試動(dòng)手去實(shí)現(xiàn)重用機(jī)制,利用的就是UIScrollView筝家。為什么要選UIScrollView洼裤,簡(jiǎn)單的原因就是它是UITableView的超類(lèi),只是后者實(shí)現(xiàn)了重用機(jī)制溪王,前者沒(méi)有腮鞍。

事先聲明一點(diǎn),為了作對(duì)比莹菱,我先做了一個(gè)非常簡(jiǎn)單的圖片瀏覽器移国,由于是實(shí)驗(yàn)性質(zhì),所以一些細(xì)節(jié)并沒(méi)有很好地去實(shí)現(xiàn)芒珠,我把主要精力放在對(duì)比沒(méi)有使用重用機(jī)制和使用了重用機(jī)制的內(nèi)存使用及滑動(dòng)的流暢度上桥狡。

先上圖說(shuō)明結(jié)果:

沒(méi)有重用機(jī)制
使用重用機(jī)制

結(jié)果非常明顯,沒(méi)有使用重用機(jī)制的皱卓,雖然我也用了懶加載的模式裹芝,但是當(dāng)我把所用圖片都看一遍后,內(nèi)存就蹭蹭蹭的往上漲了娜汁,沒(méi)有下降的痕跡嫂易;而使用了重用機(jī)制,當(dāng)只有l(wèi)oad到大一點(diǎn)的圖片的時(shí)候掐禁,內(nèi)存占用才會(huì)上漲怜械,但是過(guò)后又會(huì)降下來(lái),可喜傅事。

接下來(lái)我會(huì)分享一下我是如何實(shí)現(xiàn)這個(gè)重用機(jī)制的缕允。因?yàn)槲沂峭ㄟ^(guò)UITableView才想到這個(gè)重用的,所以我所有的思路都是參考我在使用UITableView時(shí)所做過(guò)的事蹭越,然后推測(cè)那樣做的目的障本,再自行實(shí)現(xiàn)代碼的。如果有不恰當(dāng)?shù)牡胤秸?qǐng)各位斧正,謝謝驾霜。

首先想到的是我在寫(xiě)TableViewCell的時(shí)候案训,在實(shí)現(xiàn)
- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
這個(gè)方法的時(shí)候,第一句寫(xiě)的就是
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"XXX"];
這句話(huà)的作用就是在Reusable池中dequeue一個(gè)可重用的粪糙,標(biāo)記為XXX的cell出來(lái)强霎,于是我們馬上可以想到,要實(shí)現(xiàn)重用蓉冈,目前需要以下幾個(gè)要素:

思路1.0

  • 存放可供重用的cell的Reusable池
  • 正在屏幕上顯示的cell的Visible池
  • 從Reusable池中取出cell的操作城舞,dequeue

分析

上述三個(gè)方面,前兩個(gè)需要我們各生成一個(gè)存儲(chǔ)對(duì)象洒擦,把用于展示的“cell”和可供重用的“cell”存放起來(lái)椿争,那這里我們有哪些可供利用呢?NSMutableArray熟嫩,NSMutableDictionary等秦踪?
參考了其他大神的說(shuō)法,Apple是利用了上述兩者掸茅,我猜想用字典的原因應(yīng)該是為不同的Identifier生成不同的key椅邓,然后每個(gè)key里面又是一個(gè)可變數(shù)組,存放對(duì)應(yīng)Identifier的cell(純屬瞎猜)昧狮【澳伲考慮到這個(gè)圖片瀏覽器目前功能簡(jiǎn)單,所以這里我選擇的是NSMutableSet逗鸣,具體好處后面會(huì)提到合住。
至于第三點(diǎn),取出cell的操作很顯然就是一個(gè)方法撒璧,于是乎我們就可以先實(shí)現(xiàn)這么幾個(gè)啦:

@property (nonatomic) NSMutableSet *visibleViews;
@property (nonatomic) NSMutableSet *reusableViews;

- (ZoomingScrollView *)dequeReusableView {
    ZoomingScrollView *reusableView = [_reusableViews anyObject];
    if (reusableView == nil) {
        reusableView = [[ZoomingScrollView alloc] initWithFrame:kScreenFrame];
    } else {
        [_reusableViews removeObject:reusableView];
    }
    return reusableView;
}

- (ZoomingScrollView *)viewAtIndex:(NSUInteger) index {
    ZoomingScrollView *zoomingView = [self dequeReusableView];
    
    // 這里有沒(méi)有什么問(wèn)題透葛?大家思考一下~
    UIImage *image = [UIImage imageNamed:imageName];
    [zoomingView setDisplayImage:image];
    
    CGFloat xPosition = index * kScreenFrame.size.width;
    CGRect viewFrame = CGRectMake(xPosition, 0, kScreenFrame.size.width, kScreenFrame.size.height);
    zoomingView.frame = viewFrame;
    zoomingView.tag = 1000 + index;
    
    [_visibleViews addObject:zoomingView];
    
    return zoomingView;
}

這里把viewAtIndex這個(gè)方法一并在類(lèi)里實(shí)現(xiàn)了,其實(shí)這里應(yīng)該要設(shè)計(jì)成由代理來(lái)實(shí)現(xiàn)卿樱,但是由于是實(shí)驗(yàn)僚害,所以沒(méi)有直接實(shí)現(xiàn),這樣比較直觀繁调。(后面我還是改成代理模式吧萨蚕。。蹄胰。這樣的耦合性很高)

dequeReusableView方法的作用就是從reusableViews取出可重用的view岳遥,當(dāng)沒(méi)有可以重用的view時(shí),就新創(chuàng)建一個(gè)對(duì)象裕寨。最后返回這個(gè)view寒随,這里的邏輯還是比較簡(jiǎn)單的。

viewAtIndex:方法中,[_visibleViews addObject:zoomingView];這句的作用是把顯示在屏幕上的view放到_visibleViews池中妻往,然后給大家一個(gè)小小的問(wèn)題,方法里用[UIImage imageNamed:imageName]這句導(dǎo)入圖片试和,這種做好好不好讯泣,有什么問(wèn)題~?

目前為止阅悍,我們已經(jīng)把思路1.0里的所有方面都實(shí)現(xiàn)了好渠,理清一下思路,我們現(xiàn)在所做的方向节视,是從Reusable池中取出view拳锚,放到Visible池中顯示到屏幕上,所以接下來(lái)我們要做的就是當(dāng)view離開(kāi)屏幕時(shí)寻行,把無(wú)需顯示的view從Visible池放入Reusable池中霍掺,還有即將進(jìn)入屏幕的view要及時(shí)創(chuàng)建。

思路2.0

  • 滑動(dòng)時(shí)即將出現(xiàn)的view
  • 把過(guò)時(shí)的view從Visible池中放入Reusable池中
  • 清理過(guò)時(shí)的view中的數(shù)據(jù)(如圖片拌蜘,data等)

分析

向左向右滑動(dòng)后杆烁,當(dāng)前的view還沒(méi)完全從屏幕中移走,此時(shí)千萬(wàn)不可把當(dāng)前view放入到Reusable池中简卧;
其次要提前生成即將出現(xiàn)的view兔魂,生成的方法可以利用思路1.0中已經(jīng)實(shí)現(xiàn)的viewAtIndex:方法;
最后根據(jù)當(dāng)前view的index举娩,把index-1和index+1以外的view全部放入Reusable池中等待重用析校。繼續(xù)上代碼:

- (void)showNewImage {
    ZoomingScrollView *previousView = nil;
    ZoomingScrollView *nextView = nil;
    
    NSInteger previousIndex = _currentIndex - 1;
    NSInteger nextIndex = _currentIndex + 1;
    
    // 滑動(dòng)時(shí)最多保留3個(gè)圖,n是當(dāng)前頁(yè)面數(shù)铜涉,加上左右的2個(gè)
    if (_currentIndex == 0) {
        // 第一張圖
        previousIndex = 0;
    } else if (_currentIndex == kTotalImage - 1) {
        // 最后一張圖
        nextIndex = kTotalImage - 1;
    }
    
    if (![self isShowingViewAtIndex:previousIndex]) {
        previousView = [self viewAtIndex:previousIndex];
    }
    if (![self isShowingViewAtIndex:nextIndex]) {
        nextView = [self viewAtIndex:nextIndex];
    }
    
    [_scrollView addSubview:previousView];
    [_scrollView addSubview:nextView];
    
    // 其余全部放到reusableViews里
    for (ZoomingScrollView *view in _visibleViews) {
        NSInteger viewIndex = view.tag - 1000;
        if (viewIndex < previousIndex || viewIndex > nextIndex) {
            [_reusableViews addObject:view];
            view.imageView.image = nil;
            // 記得從主視圖中remove
            [view removeFromSuperview];
        }
    }
    
    // 從visibleViews里刪除剛剛?cè)サ舻膙iew
    [_visibleViews minusSet:_reusableViews];    
}

其實(shí)后來(lái)想了一下智玻,到底需不需要保留左右兩個(gè)view。因?yàn)槲野裇crollView設(shè)成了pagingEnable骄噪,每次只滑過(guò)一個(gè)頁(yè)面尚困,所以在屏幕上最多也只會(huì)同時(shí)出現(xiàn)2個(gè)頁(yè)面,那再減少保留一個(gè)view應(yīng)該也是可以的链蕊。但是當(dāng)時(shí)在寫(xiě)代碼的時(shí)候老是會(huì)出現(xiàn)下標(biāo)事甜、Rect等問(wèn)題(設(shè)想不周全。滔韵。逻谦。),所以最好為了保險(xiǎn)起見(jiàn)還是多留一個(gè)吧……后面優(yōu)化一下應(yīng)該就不需要了~

還有陪蜻,這里回答一下為什么我選擇NSMutableSet邦马,原因是[_visibleViews minusSet:_reusableViews];,這里一句話(huà)就可以把Visible池中的過(guò)時(shí)view去掉,節(jié)省了遍歷的時(shí)間滋将,而且又方便邻悬。

到目前為止,重用機(jī)制所需要的操作我們都實(shí)現(xiàn)了随闽,那我們應(yīng)該在什么時(shí)機(jī)去調(diào)用這些操作呢父丰?
由于在我們開(kāi)始滑動(dòng)的時(shí)候,無(wú)論是左滑還是右滑掘宪,只要滑動(dòng)了蛾扇,前一個(gè)或下一個(gè)view就會(huì)馬上出現(xiàn)了,所以上面的showNewImage方法應(yīng)該是在滑動(dòng)開(kāi)始時(shí)就馬上調(diào)用魏滚。
沒(méi)錯(cuò)镀首,我們應(yīng)該要在UIScrollViewDelegate的方法- (void)scrollViewDidScroll:(UIScrollView *)scrollView中調(diào)用showNewImage方法。
并且鼠次,當(dāng)滑動(dòng)結(jié)束后更哄,及時(shí)更新當(dāng)前顯示的view的index。

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    [self showNewImage];
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    _currentIndex = _scrollView.contentOffset.x / 320;
}

好了须眷,到這里為止竖瘾,重用機(jī)制的思路和調(diào)用時(shí)機(jī)都講完了,其實(shí)里面的思路都比較簡(jiǎn)單花颗,在實(shí)現(xiàn)過(guò)程中計(jì)較容易出錯(cuò)的是由于index弄錯(cuò)而導(dǎo)致了view顯示不正確捕传,或者整個(gè)app崩掉。這時(shí)要需要來(lái)個(gè)斷點(diǎn)調(diào)試扩劝,把斷點(diǎn)設(shè)在新加入的代碼中庸论,一步一步看各個(gè)變量,對(duì)象的值有沒(méi)有異常棒呛。

最后聂示,回到思路1.0代碼中的問(wèn)題,那樣的圖片導(dǎo)入方法好不好簇秒,有沒(méi)有問(wèn)題鱼喉?

答案肯定是有問(wèn)題的,而且問(wèn)題大得很趋观。
UIImage *image = [UIImage imageNamed:imageName];導(dǎo)入的圖就算在后面把image設(shè)成nil扛禽,圖片也是不會(huì)自動(dòng)釋放的。這個(gè)方法適用場(chǎng)景是UI的背景圖皱坛,或者需要大量重復(fù)使用同一個(gè)圖片的地方编曼。

而我們的圖片瀏覽器顯然不符合上述情況,圖片無(wú)法自動(dòng)釋放直接導(dǎo)致內(nèi)存不斷上漲剩辟,那再多的重用也是百搭……
所以這里用UIImage *image = [UIImage imageWithContentsOfFile:path];更好掐场,圖片可以自動(dòng)釋放往扔,正合我們心意~

- (ZoomingScrollView *)viewAtIndex:(NSUInteger) index {
    ZoomingScrollView *zoomingView = [self dequeReusableView];
    
    NSString *imageName = [NSString stringWithFormat:@"%lu", index+1];
//    // 這是一個(gè)坑爹的方法,用這個(gè)方法load的圖無(wú)法自動(dòng)釋放
//    UIImage *image = [UIImage imageNamed:imageName];
    NSString *path = [[NSBundle mainBundle] pathForResource:imageName ofType:@"jpg"];
    UIImage *image = [UIImage imageWithContentsOfFile:path];
    [zoomingView setDisplayImage:image];
    
    CGFloat xPosition = index * kScreenFrame.size.width;
    CGRect viewFrame = CGRectMake(xPosition, 0, kScreenFrame.size.width, kScreenFrame.size.height);
    zoomingView.frame = viewFrame;
    zoomingView.tag = 1000 + index;
    
    [_visibleViews addObject:zoomingView];
    
    return zoomingView;
}

先到這里吧熊户,感謝觀看萍膛,謝謝!
如有不足嚷堡,懇請(qǐng)不吝賜教卦羡!及時(shí)私信我吧~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市麦到,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌欠肾,老刑警劉巖瓶颠,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異刺桃,居然都是意外死亡粹淋,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)瑟慈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)桃移,“玉大人,你說(shuō)我怎么就攤上這事葛碧〗杞埽” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵进泼,是天一觀的道長(zhǎng)蔗衡。 經(jīng)常有香客問(wèn)我,道長(zhǎng)乳绕,這世上最難降的妖魔是什么绞惦? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮洋措,結(jié)果婚禮上济蝉,老公的妹妹穿的比我還像新娘。我一直安慰自己菠发,他們只是感情好王滤,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著雷酪,像睡著了一般淑仆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上哥力,一...
    開(kāi)封第一講書(shū)人閱讀 52,394評(píng)論 1 310
  • 那天蔗怠,我揣著相機(jī)與錄音墩弯,去河邊找鬼。 笑死寞射,一個(gè)胖子當(dāng)著我的面吹牛渔工,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播桥温,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼引矩,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了侵浸?” 一聲冷哼從身側(cè)響起旺韭,我...
    開(kāi)封第一講書(shū)人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎掏觉,沒(méi)想到半個(gè)月后区端,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡澳腹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年织盼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片酱塔。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡沥邻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤启具,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站芦瘾,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏集畅。R本人自食惡果不足惜近弟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望挺智。 院中可真熱鬧祷愉,春花似錦、人聲如沸赦颇。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)媒怯。三九已至订讼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間扇苞,已是汗流浹背欺殿。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工寄纵, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人脖苏。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓程拭,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親棍潘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子恃鞋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,302評(píng)論 25 707
  • 好文章,轉(zhuǎn)載一下,有機(jī)會(huì)好好研究下 今天在研究SDWebImage和ASIHTTPRequest實(shí)現(xiàn)網(wǎng)絡(luò)圖片異步加...
    Apollo2016閱讀 1,987評(píng)論 0 2
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件亦歉、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,121評(píng)論 4 61
  • 工作不是很忙恤浪,恰好有看書(shū)碼字的時(shí)間。 我從不認(rèn)為自己是勇敢的肴楷,我希望自己是一朵被蝴蝶青睞的艷麗牡丹资锰,可我卻是一株渺...
    潔若依依閱讀 288評(píng)論 0 0
  • 我們每個(gè)人都是天上掉下來(lái)的星星,來(lái)到人世間就變得十分微小阶祭,微小到塵埃里。但又不甘落寞直秆,努力發(fā)光濒募,希望在黑夜降臨的時(shí)...
    風(fēng)箏_92f3閱讀 458評(píng)論 0 0