系列文章:
吶跪腹,也好久沒寫博客了褂删,為什么呢?
因?yàn)閼邪 ?/h3>
不管你信與不信冲茸,這都不是真的屯阀。
因?yàn)樽罱镜捻?xiàng)目要上二版,然而我還沒有提前完成他的決心轴术,所以难衰,你懂得。
今天說點(diǎn)什么呢逗栽,恩盖袭,還是說說tabV相關(guān)的吧,之前的存貨了。
tableView優(yōu)化之高度緩存功能
日常開發(fā)中鳄虱,tableView的使用率很高弟塞,所以相對(duì)tableView的優(yōu)化來說可以做很多很多的事情。很多情況下拙已,我們的每一個(gè)cell都沒有一個(gè)固定的高度宣肚,而是根據(jù)cell里面的內(nèi)容自適應(yīng)高度的。那么每次當(dāng)我們cell將要出現(xiàn)在屏幕的時(shí)候悠栓,系統(tǒng)都會(huì)去計(jì)算cell的高度霉涨。如果說我能通過某種手段,在首次計(jì)算的時(shí)候惭适,將每個(gè)cell對(duì)應(yīng)的高度保存下載笙瑟,當(dāng)下次需要用到cell高度的時(shí)候再?gòu)谋4娴牡胤饺〕觯瑥亩鴾p少了計(jì)算量癞志,來達(dá)到優(yōu)化的目的往枷。
因此應(yīng)運(yùn)而生了這套高度緩存的算法。
在此聲明凄杯,這套算法不是老司機(jī)原創(chuàng)
错洁,是整合并優(yōu)化了外國(guó)一位大神的源碼。
再次聲明戒突,
這原來是國(guó)人算法屯碴,揚(yáng)我國(guó)威啊,原型名
UITableView-FDTemplateLayoutCell膊存。
這篇博客中你可能會(huì)用到
- category 导而、subclass、protocol之間的區(qū)別
- 自動(dòng)布局相關(guān)
- runTime動(dòng)態(tài)綁定
- tableViewCell重用的相關(guān)
恩隔崎,其實(shí)東西并不多今艺,重要的還是一個(gè)思想。
老規(guī)矩爵卒,還是先放全部代碼虚缎。
#import "UITableView+HeightCache.h"
#import <objc/runtime.h>
@implementation UITableView (HeightCache)
#pragma mark ---接口方法---
-(CGFloat)DW_CalculateCellWithIdentifier:(NSString *)identifier
indexPath:(NSIndexPath *)indexPath
configuration:(void(^)(id cell))configuration
{
if(self.bounds.size.width != 0)//防止初始寬度為0(如autoLayout初次加載時(shí))
{
if (!identifier.length || !indexPath) {//非空判斷
return 0;
}
NSString * key = [self.cache makeKeyWithIdentifier:identifier indexPath:indexPath];//制作key
if ([self.cache existInCacheByKey:key]) {//如果key存在
return [self.cache heightFromCacheWithKey:key];//從字典中取出高
}
CGFloat height = [self DW_CalCulateCellWithIdentifier:identifier configuration:configuration];//不存在則計(jì)算高度
[self.cache cacheHeight:height byKey:key];//并緩存
return height;
}
return 0;
}
-(void)DW_RemoveHeightCacheWithIdentifier:(NSString *)identifier
indexPath:(NSIndexPath *)indexPath
numberOfRows:(NSInteger)rows
{
[self.cache removeHeightByIdentifier:identifier indexPath:indexPath numberOfRows:rows];
}
-(void)DW_RemoveAllHeightCache
{
[self.cache removeAllHeight];
}
-(void)DW_InsertCellToIndexPath:(NSIndexPath *)indexPath
withIdentifier:(NSString *)identifier
numberOfRows:(NSInteger)rows
{
[self.cache insertCellToIndexPath:indexPath withIdentifier:identifier numberOfRows:rows toDictionaryForCache:self.cache.dicHeightCurrent];
}
-(void)DW_MoveCellFromIndexPath:(NSIndexPath *)sourceIndexPath
sourceIndexPathNumberOfRows:(NSInteger)sourceRows
toIndexPath:(NSIndexPath *)destinationIndexPath
destinationIndexPathNumberOfRows:(NSInteger)destinationRows
withIdentifier:(NSString *)identifier
{
[self.cache moveCellFromIndexPath:sourceIndexPath sourceSectionNumberOfRows:sourceRows toIndexPath:destinationIndexPath destinationSectionNumberOfRows:destinationRows withIdentifier:identifier];
}
#pragma mark ---工具方法---
///從重用池中返回計(jì)算用的cell
-(__kindof UITableViewCell *)DW_CalculateCellWithIdentifier:(NSString *)identifier
{
if (!identifier.length) {
return nil;
}
NSMutableDictionary <NSString * ,UITableViewCell *> *DicForTheUniqueCalCell = objc_getAssociatedObject(self, _cmd);//利用runtime取出tableV綁定的存有cell的字典
if (!DicForTheUniqueCalCell) {
DicForTheUniqueCalCell = [NSMutableDictionary dictionary];//如果取不到則新建并綁定
objc_setAssociatedObject(self, _cmd, DicForTheUniqueCalCell, OBJC_ASSOCIATION_RETAIN_NONATOMIC);//動(dòng)態(tài)綁定(綁定目標(biāo),關(guān)鍵字钓株,綁定者实牡,策略)
}
//以上只是為了只綁定一個(gè)字典,類比懶加載
UITableViewCell * cell = DicForTheUniqueCalCell[identifier];
if (!cell) {
cell = [self dequeueReusableCellWithIdentifier:identifier];//從重用池中取一個(gè)cell用來計(jì)算享幽,必須以本方式從重用池中取铲掐,若以indexPath方式取由于-heightForRowAtIndexPath方法會(huì)造成循環(huán)拾弃。
cell.contentView.translatesAutoresizingMaskIntoConstraints = NO;//開啟約束
cell.JustForCal = YES;//標(biāo)記只用來計(jì)算
DicForTheUniqueCalCell[identifier] = cell;
}
//同上值桩,保證只有一個(gè)用來計(jì)算的cell
return cell;
}
///根據(jù)重用表示取出cell并操作cell后,計(jì)算高度
-(CGFloat)DW_CalCulateCellWithIdentifier:(NSString *)identifier
configuration:(void(^)(id cell))configuration
{
if (!identifier.length) {
return 0;
}
UITableViewCell * cell = [self DW_CalculateCellWithIdentifier:identifier];
[cell prepareForReuse];//放回重用池
if (configuration) {
configuration(cell);//對(duì)cell進(jìn)行操作
}
return [self DW_CalculateCellHeightWithCell:cell];
}
///根據(jù)cell計(jì)算cell的高度
-(CGFloat)DW_CalculateCellHeightWithCell:(UITableViewCell *)cell
{
CGFloat width = self.bounds.size.width;
//根據(jù)輔助視圖校正width
if (cell.accessoryView) {
width -= cell.accessoryView.bounds.size.width + 16;
}
else
{
static const CGFloat accessoryWidth[] = {
[UITableViewCellAccessoryNone] = 0,
[UITableViewCellAccessoryDisclosureIndicator] = 34,
[UITableViewCellAccessoryDetailDisclosureButton] = 68,
[UITableViewCellAccessoryCheckmark] = 40,
[UITableViewCellAccessoryDetailButton] = 48
};
width -= accessoryWidth[cell.accessoryType];
}
CGFloat height = 0;
if (!cell.NoAutoSizing && width > 0) {//如果不是非自適應(yīng)模式則添加約束后計(jì)算約束后高度
NSLayoutConstraint * widthConstraint = [NSLayoutConstraint constraintWithItem:cell.contentView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:width];//創(chuàng)建約束
[cell.contentView addConstraint:widthConstraint];//添加約束
height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;//計(jì)算高度
[cell.contentView removeConstraint:widthConstraint];//移除約束
}
if (height == 0) {//如果約束錯(cuò)誤可能導(dǎo)致計(jì)算結(jié)果為零豪椿,則以自適應(yīng)模式再次計(jì)算
height = [cell sizeThatFits:CGSizeMake(width, 0)].height;
}
if (height == 0) {//如果計(jì)算仍然為0奔坟,則給出默認(rèn)高度
height = 44;
}
if (self.separatorStyle != UITableViewCellSeparatorStyleNone) {//如果不為無分割線模式則添加分割線高度
height += 1.0 /[UIScreen mainScreen].scale;
}
return height;
}
#pragma mark ---setter携栋、getter---
-(HeightCache *)cache//懶加載形式
{
HeightCache * cacheTemp = objc_getAssociatedObject(self, _cmd);
if (!cacheTemp) {
cacheTemp = [HeightCache new];
objc_setAssociatedObject(self, _cmd, cacheTemp, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return cacheTemp;
}
-(void)setCache:(HeightCache *)cache
{
objc_setAssociatedObject(self, @selector(cache), cache, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
@implementation UITableViewCell (HeightCacheCell)
#pragma mark ---setter、getter---
-(BOOL)NoAutoSizing
{
return [objc_getAssociatedObject(self, _cmd) boolValue];
}
-(void)setNoAutoSizing:(BOOL)NoAutoSizing
{
objc_setAssociatedObject(self, @selector(NoAutoSizing), @(NoAutoSizing), OBJC_ASSOCIATION_RETAIN);//關(guān)鍵字用getter的方法名咳秉,為保持關(guān)鍵字一致
}
-(BOOL)JustForCal
{
return [objc_getAssociatedObject(self, _cmd) boolValue];
}
-(void)setJustForCal:(BOOL)JustForCal
{
objc_setAssociatedObject(self, @selector(JustForCal), @(JustForCal), OBJC_ASSOCIATION_RETAIN);
}
@end
@implementation HeightCache
///制作key
-(NSString *)makeKeyWithIdentifier:(NSString *)identifier
indexPath:(NSIndexPath *)indexPath
{
return [NSString stringWithFormat:@"%@S%ldR%ld",identifier,indexPath.section,indexPath.row];
}
///高度是否存在
-(BOOL)existInCacheByKey:(NSString *)key
{
NSNumber * value = [self.dicHeightCurrent valueForKey:key];
return (value && ![value isEqualToNumber:@-1]);
}
///取出緩存的高度
-(CGFloat)heightFromCacheWithKey:(NSString *)key
{
NSNumber * value = [self.dicHeightCurrent valueForKey:key];
if ([self is64bit]) {
return [value doubleValue];
}
return [value floatValue];
}
///64位判斷
- (BOOL)is64bit
{
#if defined(__LP64__) && __LP64__
return YES;
#else
return NO;
#endif
}
///高度緩存
-(void)cacheHeight:(CGFloat)height
byKey:(NSString *)key
{
[self.dicHeightCurrent setValue:@(height) forKey:key];
}
///根據(jù)key刪除緩存
-(void)removeHeightByIdentifier:(NSString *)identifier
indexPath:(NSIndexPath *)indexPath
numberOfRows:(NSInteger)rows
{
if (indexPath.row < rows) {
for (int i = 0; i < rows - 1 - indexPath.row; i ++) {
NSIndexPath * indexPathA = [NSIndexPath indexPathForRow:indexPath.row + i inSection:indexPath.section];
NSLog(@"%ld,%ld",indexPathA.row,indexPathA.section);
NSIndexPath * indexPathB = [NSIndexPath indexPathForRow:indexPath.row + i + 1 inSection:indexPath.section];
NSLog(@"%ld,%ld",indexPathB.row,indexPathB.section);
[self exchangeValueForIndexPathA:indexPathA andIndexPathB:indexPathB withIdentifier:identifier dictionary:self.dicHeightCacheH];
[self exchangeValueForIndexPathA:indexPathA andIndexPathB:indexPathB withIdentifier:identifier dictionary:self.dicHeightCacheV];
}
NSIndexPath * indexPathC = [NSIndexPath indexPathForRow:rows - 1 inSection:indexPath.section];
NSString * key = [self makeKeyWithIdentifier:identifier indexPath:indexPathC];
[self.dicHeightCacheH removeObjectForKey:key];
[self.dicHeightCacheV removeObjectForKey:key];
}
}
///刪除所有緩存
-(void)removeAllHeight
{
[self.dicHeightCacheH removeAllObjects];
[self.dicHeightCacheV removeAllObjects];
}
///插入cell是插入value
-(void)insertCellToIndexPath:(NSIndexPath *)indexPath
withNumberOfRows:(NSInteger)rows
heightNumber:(NSNumber *)height
identifier:(NSString *)identifier
toDictionaryForCache:(NSMutableDictionary *)dic
{
if (indexPath.row < rows + 1) {
[self insertCellToIndexPath:indexPath withIdentifier:identifier numberOfRows:rows toDictionaryForCache:dic];
NSString * key = [self makeKeyWithIdentifier:identifier indexPath:indexPath];
[dic setValue:height forKey:key];
}
}
-(void)insertCellToIndexPath:(NSIndexPath *)indexPath withIdentifier:(NSString *)identifier numberOfRows:(NSInteger)rows toDictionaryForCache:(NSMutableDictionary *)dic
{
if (indexPath.row < rows + 1) {
for (int i = 0; i < rows - indexPath.row; i ++) {
NSIndexPath * indexPathA = [NSIndexPath indexPathForRow:rows - i inSection:indexPath.section];
NSIndexPath * indexPathB = [NSIndexPath indexPathForRow:rows - i - 1 inSection:indexPath.section];
[self exchangeValueForIndexPathA:indexPathA andIndexPathB:indexPathB withIdentifier:identifier dictionary:dic];
}
}
}
///移動(dòng)cell時(shí)交換value
-(void)moveCellFromIndexPath:(NSIndexPath *)sourceIndexPath
sourceSectionNumberOfRows:(NSInteger)sourceRows
toIndexPath:(NSIndexPath *)destinationIndexPath
destinationSectionNumberOfRows:(NSInteger)destinationRows
withIdentifier:(NSString *)identifier
{
if (sourceIndexPath.section == destinationIndexPath.section) {
[self moveCellInSectionFromIndexPath:sourceIndexPath toIndexPath:destinationIndexPath withIdentifier:identifier];
}
else
{
[self moveCellOutSectionFromIndexPath:sourceIndexPath sourceSectionNumberOfRows:sourceRows toIndexPath:destinationIndexPath destinationSectionNumberOfRows:destinationRows withIdentifier:identifier];
}
}
///組內(nèi)移動(dòng)
-(void)moveCellInSectionFromIndexPath:(NSIndexPath *)sourceIndexPath
toIndexPath:(NSIndexPath *)destinationIndexPath
withIdentifier:(NSString *)identifier
{
NSInteger rowA = sourceIndexPath.row;
NSInteger rowB = destinationIndexPath.row;
for (int i = 0; i < (MAX(rowA, rowB) - MIN(rowA, rowB)); i ++) {
NSIndexPath * indexPathA = [NSIndexPath indexPathForRow:MIN(rowA, rowB) + i inSection:sourceIndexPath.section];
NSIndexPath * indexPathB = [NSIndexPath indexPathForRow:MIN(rowA, rowB) + i + 1 inSection:sourceIndexPath.section];
[self exchangeValueForIndexPathA:indexPathA andIndexPathB:indexPathB withIdentifier:identifier dictionary:self.dicHeightCacheV];
[self exchangeValueForIndexPathA:indexPathA andIndexPathB:indexPathB withIdentifier:identifier dictionary:self.dicHeightCacheH];
}
}
///組外移動(dòng)
-(void)moveCellOutSectionFromIndexPath:(NSIndexPath *)sourceIndexPath
sourceSectionNumberOfRows:(NSInteger)sourceRows
toIndexPath:(NSIndexPath *)destinationIndexPath
destinationSectionNumberOfRows:(NSInteger)destinationRows
withIdentifier:(NSString *)identifier
{
NSNumber * numberH;
NSNumber * numberV;
NSLog(@"%ld",sourceIndexPath.row);
if (sourceIndexPath.row < sourceRows) {
NSString * key = [self makeKeyWithIdentifier:identifier indexPath:sourceIndexPath];
numberH = self.dicHeightCacheH[key];
numberV = self.dicHeightCacheV[key];
[self removeHeightByIdentifier:identifier indexPath:sourceIndexPath numberOfRows:sourceRows];
}
NSLog(@"%ld,%ld",destinationIndexPath.row,destinationIndexPath.section);
[self insertCellToIndexPath:destinationIndexPath withNumberOfRows:destinationRows heightNumber:numberH identifier:identifier toDictionaryForCache:self.dicHeightCacheH];
[self insertCellToIndexPath:destinationIndexPath withNumberOfRows:destinationRows heightNumber:numberV identifier:identifier toDictionaryForCache:self.dicHeightCacheV];
}
///根據(jù)indexPath交換兩個(gè)Key
-(void)exchangeValueForIndexPathA:(NSIndexPath *)indexPathA
andIndexPathB:(NSIndexPath *)indexPathB
withIdentifier:(NSString *)identifier
dictionary:(NSMutableDictionary *)dic
{
NSString * keyA = [self makeKeyWithIdentifier:identifier indexPath:indexPathA];
NSString * keyB = [self makeKeyWithIdentifier:identifier indexPath:indexPathB];
NSNumber * Temp = dic[keyA];
dic[keyA] = dic[keyB];
dic[keyB] = Temp;
}
#pragma mark ---懶加載---
-(NSMutableDictionary *)dicHeightCacheH
{
if (!_dicHeightCacheH) {
_dicHeightCacheH = [NSMutableDictionary dictionary];
}
return _dicHeightCacheH;
}
-(NSMutableDictionary *)dicHeightCacheV
{
if (!_dicHeightCacheV) {
_dicHeightCacheV = [NSMutableDictionary dictionary];
}
return _dicHeightCacheV;
}
-(NSMutableDictionary *)dicHeightCurrent//根據(jù)系統(tǒng)狀態(tài)返回對(duì)應(yīng)字典
{
return UIDeviceOrientationIsPortrait([UIDevice currentDevice].orientation)?self.dicHeightCacheV:self.dicHeightCacheH;
}
@end
這么多婉支,你這個(gè)騙子!
你是不是這么想得澜建。
別慌向挖,東西不多,因?yàn)檫@是一個(gè)category炕舵,復(fù)用性非常高何之,所以老司機(jī)想盡量減少文件個(gè)數(shù),這樣集成的時(shí)候也方便不是咽筋。
所以溶推,老司機(jī)把三個(gè)類寫在一個(gè)文件里了
。
之前有人問過我把幾個(gè)類寫在一個(gè)文件中有什么好處么
奸攻?
老司機(jī)目前只能說蒜危,除了看上去裝逼點(diǎn),別的沒什么卵用
睹耐。
可能是老司機(jī)的理解不深辐赞,不過為了集成方便我還是寫在一個(gè)文件里了。
廢話不多說硝训,分段講解吧占拍。
分段解析
為什么選擇category而不是subclass
我想很多人都會(huì)有疑問,為什么選擇category而不是繼承捎迫,畢竟category不能添加屬性,用起來不是很方便晃酒。說到這又要老生常談了。
Protocol只是聲明一套接口窄绒,并不能提供具體實(shí)現(xiàn)贝次,變相的也算是一種抽象基類的實(shí)現(xiàn)方式(OC本身語(yǔ)法并不支持抽象基類)。
Category可以為已有的類提供額外的接口和具體的實(shí)現(xiàn)彰导。
Protocol只能提供一套公用的接口聲明蛔翅,并不能提供具體實(shí)現(xiàn),它的行為是位谋,我只負(fù)責(zé)聲明山析,而不管誰(shuí)去實(shí)現(xiàn),去如何實(shí)現(xiàn)掏父。這樣的話笋轨,我定義一套接口,可以使任意的類都用不同的方式去實(shí)現(xiàn)接口中的方法,就是為遵守了protocol的類提供了一些額外訪問這個(gè)類的一些接口爵政,像delegate和dataSource用protocol實(shí)現(xiàn)是最好的仅讽。
Category是對(duì)一個(gè)功能完備的類的一種補(bǔ)充、擴(kuò)展钾挟,就像一個(gè)東西基本功能都完成了洁灵,可以用category為這個(gè)類添加不同的組件,使得這個(gè)類能夠適應(yīng)不同情況的需求(但是這些不同需求最核心的需求要一致)掺出。當(dāng)然徽千,當(dāng)某個(gè)類非常大的時(shí)候,使用category可以按照不同的功能將類的實(shí)現(xiàn)分在不同的模塊中汤锨。還有罐栈,雖然category可以訪問已有類的實(shí)例變量,但不能創(chuàng)建新的實(shí)例變量泥畅,如果要?jiǎng)?chuàng)建新的實(shí)例變量荠诬,請(qǐng)使用繼承。
繼承位仁,它基于Protocol和Category之間柑贞,既可以像protocol一樣只提供純粹的接口,也可以像Category一樣提供接口的完整實(shí)現(xiàn)聂抢,可以自由定義類的實(shí)例變量(這一點(diǎn)钧嘶,Protocol倒是可以聲明實(shí)例變量,但是也僅僅是聲明而已)琳疏,而且繼承還可以對(duì)類以后的方法進(jìn)行改寫有决,所以繼承的力量是最強(qiáng)大的。
在iOS開發(fā)中空盼,繼承是完全可以完成protocol和category的功能的书幕,那么在開發(fā)過程中多多使用繼承體系可好?
需要注意的是使用繼承還有很大的代價(jià)問題揽趾。使用繼承來進(jìn)行擴(kuò)展是一種耦合度很高的行為台汇,對(duì)父類可以說是完全依賴,如果繼承體系太過復(fù)雜篱瞎,會(huì)造成難以維護(hù)的問題苟呐。如果僅僅只是對(duì)類進(jìn)行擴(kuò)展,并不建議使用繼承俐筋,畢竟使用protocol和category是很簡(jiǎn)單牵素、輕松的。除此之外澄者,在開發(fā)過程中笆呆,我們應(yīng)該盡量將界面请琳、功能相似的類的代碼提取到基類里面,然后各個(gè)子類繼承自這個(gè)基類腰奋,實(shí)現(xiàn)各自的其他特殊部分单起。這樣可以大大的優(yōu)化代碼抱怔,如果需要修改的話劣坊,只需要這倒對(duì)應(yīng)子類修改即可。
是不是感覺老司機(jī)屌屌的屈留,嘖嘖嘖局冰,我百度的。
我選擇category就一個(gè)原因灌危,擴(kuò)展方便康二,二次開發(fā)也方便。
類目拆解
老司機(jī)說過勇蝙,這一坨代碼是三個(gè)類寫在了一個(gè)文件里沫勿,他們都是什么呢?
- UITableView的category
- UITableViewCell的category
- 一個(gè)NSObject的子類
他們分別是做什么的呢味混?
首先产雹,給UITableView添加category是因?yàn)闉榱?code>實(shí)現(xiàn)高度緩存,我的方案是在計(jì)算高度的時(shí)候就模擬數(shù)據(jù)填充翁锡,從而計(jì)算出該cell的高度蔓挖,所以,tableView應(yīng)該有填充數(shù)據(jù)和計(jì)算高度的方法馆衔。故為其添加分類瘟判。
而那個(gè)繼承于NSObject的子類就是用來存儲(chǔ)計(jì)算出來的高度的
,這樣當(dāng)下次需要計(jì)算的時(shí)候直接從這里取出即可角溃。
至于那個(gè)UITableViewCell的類目是為了給cell添加兩個(gè)標(biāo)識(shí)符
拷获,一個(gè)用來判斷當(dāng)前cell是否需要一autolayout進(jìn)行繪制,另一個(gè)是用來區(qū)分計(jì)算用的cell還是展示用的cell减细。這點(diǎn)現(xiàn)在可能你還不懂刀诬,一會(huì)我們會(huì)做相應(yīng)介紹。
UITableViewCell的category
為什么先說這個(gè)類目呢邪财,因?yàn)檫@個(gè)類目的內(nèi)容最少陕壹,而且只用到了runtime的動(dòng)態(tài)綁定,可以拿出來單獨(dú)介紹一下runtime的相關(guān)知識(shí)树埠。
什么是runtime
run糠馆,運(yùn)行,time怎憋,時(shí)間又碌,那么runtime就是運(yùn)行時(shí)九昧。恩,簡(jiǎn)單不毕匀?
然后我們說說铸鹰。。皂岔。誒誒誒蹋笼,別打別打,開個(gè)玩笑躁垛。
runtime剖毯,簡(jiǎn)稱運(yùn)行時(shí),是系統(tǒng)在運(yùn)行期間的一些機(jī)制
教馆。而對(duì)于OC來講呢逊谋,其中最重要的就是消息機(jī)制
。
C語(yǔ)言呢土铺,我們調(diào)用函數(shù)
胶滋,在編譯期
就已經(jīng)確定了要調(diào)用那個(gè)函數(shù),而且整個(gè)過程是順序執(zhí)行
的悲敷。
而在OC中呢究恤,我們是講消息發(fā)送
的。而且我們是等待某個(gè)信號(hào)觸發(fā)才執(zhí)行
代碼的镀迂。我們知道OC事實(shí)上是基于C的丁溅,那他是怎么實(shí)現(xiàn)這套轉(zhuǎn)換的呢?就是通過runtime去實(shí)現(xiàn)
的探遵。
不信窟赏?不信跟我來做個(gè)試驗(yàn)。
新開一個(gè)工程箱季,刪掉所有文件涯穷,只留下info.plist和main.m
。并且將引入的頭文件刪除掉藏雏。
再建一個(gè)類
拷况,里面隨便寫一個(gè)方法的聲明和實(shí)現(xiàn)。
然后在main.m中引入這個(gè)類
掘殴,初始化并調(diào)用剛才聲明的方法
赚瘦,如下圖。
此時(shí)奏寨,打開我們的終端起意。找到剛才的工程的main.m,并且輸入
clang -rewrite-objc main.m
,點(diǎn)擊回車病瞳。稍等你就會(huì)看到提示轉(zhuǎn)換完成揽咕。
這回在finder中找到工程的文件夾缘回,在main.m同級(jí)文件夾下多了一個(gè)文件main.cpp
懂扼,這就是轉(zhuǎn)換完的文件。我們看到代碼還是很多的萄唇。直接拖到最下方
我們大概能看到點(diǎn)認(rèn)識(shí)的了般堆,int main诸狭。专甩。遮晚。
這就是我們剛才main函數(shù)里面的實(shí)現(xiàn)
。
看不懂掘而?待我?guī)湍戕坜坌冢サ粢恍╊愋娃D(zhuǎn)換用的修飾符后剩下如下代碼于购,是不是清晰多了袍睡?
先看被我框選中的代碼,objc_msgSend是說發(fā)送消息
肋僧,他有兩個(gè)參數(shù)
斑胜,一個(gè)是實(shí)例
,一個(gè)是方法
嫌吠。objc_getClass通過字符串獲取到這個(gè)類
止潘。sel_registerName通過字符串獲取方法
。所以這句話的意思就是給這個(gè)類發(fā)送了這個(gè)消息辫诅,消息內(nèi)容就是一個(gè)方法
凭戴。
隨后就容易了,給這個(gè)實(shí)例發(fā)送一個(gè)sayHello的消息炕矮,參數(shù)是后面的字符串么夫。
通過這里我們知道我們OC的語(yǔ)言是怎么實(shí)現(xiàn)的了吧,就是通過runtime轉(zhuǎn)化成了C++的代碼
肤视,然后進(jìn)行運(yùn)行档痪。
從這你也應(yīng)該知道為什么OC中叫發(fā)送消息
,不叫函數(shù)調(diào)用了吧邢滑。
另外你還應(yīng)該知道為什么OC中方法只聲明
腐螟,不實(shí)現(xiàn)
,編譯時(shí)只報(bào)警不報(bào)錯(cuò)
困后,運(yùn)行時(shí)crash
是為什么了吧乐纸。
既然說到這里就多少說一說C與OC吧。之前老司機(jī)說過摇予,OC是基于C的汽绢,那么C語(yǔ)言中是沒有對(duì)象這個(gè)概念呢,我們的對(duì)象又是什么呢趾盐?
右手啊庶喜,程序員哪有什么對(duì)象小腊,嘖嘖嘖。
除了右手久窟,還有結(jié)構(gòu)體秩冈,OC的對(duì)象就是C語(yǔ)言中的結(jié)構(gòu)體
。
我們看到了斥扛,每個(gè)類都是一個(gè)都是一個(gè)結(jié)構(gòu)體入问,其中有各種指針
,指向一個(gè)類的各種參數(shù)
稀颁,父類芬失、屬性列表、方法列表等等
匾灶。
所以說當(dāng)我們聲明了類的方法棱烂,方法列表里面就有這個(gè)方法了,然后編譯通過了阶女,然后調(diào)用的時(shí)候颊糜,方法選擇器去本類的方法列表里去尋找方法的實(shí)現(xiàn),如果沒有實(shí)現(xiàn)秃踩,則去其父類中尋找衬鱼,如果在沒有通過一系列消息轉(zhuǎn)發(fā)機(jī)制
會(huì)一直找下去,直到最后也沒有找到這個(gè)方法的實(shí)現(xiàn)就crash了憔杨。關(guān)于消息轉(zhuǎn)發(fā)鸟赫,其實(shí)還有很多東西,但是在這里講就又扯遠(yuǎn)了消别,所以等下期吧=抛蚤。=
本例中我們用runtime做了些什么呢?
使用runtime動(dòng)態(tài)為category綁定屬性
之前老司機(jī)說過妖啥,category是不能添加屬性的霉颠。那我又要為其添加兩個(gè)標(biāo)識(shí)符,只能使用runtime去動(dòng)態(tài)綁定
了荆虱,在類的屬性列表
里面通過runtime添加
上這個(gè)屬性蒿偎,那我就可以使用這個(gè)屬性了。
首先在.h的UITableCell的category的@interface之中添加兩個(gè)屬性
@interface UITableViewCell (HeightCacheCell)
@property (assign ,nonatomic)BOOL JustForCal;//計(jì)算用的cell標(biāo)識(shí)符(將計(jì)算用的cell與正常顯示的cell進(jìn)行區(qū)分怀读,避免不必要的ui響應(yīng))
@property (assign ,nonatomic)BOOL NoAutoSizing;//不適用autoSizing標(biāo)識(shí)符(不依靠約束計(jì)算诉位,只進(jìn)行自適應(yīng))
@end
這是我為期添加的兩個(gè)屬性,具體有什么作用菜枷,下文中會(huì)提到的苍糠,先別急。
然后在.m中添加兩個(gè)屬性的setter啤誊、getter方法
@implementation UITableViewCell (HeightCacheCell)
#pragma mark ---setter岳瞭、getter---
-(BOOL)NoAutoSizing
{
return [objc_getAssociatedObject(self, _cmd) boolValue];
}
-(void)setNoAutoSizing:(BOOL)NoAutoSizing
{
objc_setAssociatedObject(self, @selector(NoAutoSizing), @(NoAutoSizing), OBJC_ASSOCIATION_RETAIN);//關(guān)鍵字用getter的方法名拥娄,為保持關(guān)鍵字一致
}
-(BOOL)JustForCal
{
return [objc_getAssociatedObject(self, _cmd) boolValue];
}
-(void)setJustForCal:(BOOL)JustForCal
{
objc_setAssociatedObject(self, @selector(JustForCal), @(JustForCal), OBJC_ASSOCIATION_RETAIN);
}
@end
這里老司機(jī)有必要說一下兩個(gè)方法
- objc_setAssociatedObject
- objc_getAssociatedObject
OC自解釋語(yǔ)言的好處就體現(xiàn)出來了,從函數(shù)名你就可以看出來瞳筏,一個(gè)是給對(duì)象設(shè)置聯(lián)系稚瘾,一個(gè)是從對(duì)象獲取聯(lián)系。反正我英語(yǔ)水平就這樣姚炕,我也沒查字典摊欠,對(duì)不對(duì)的我就不深究了。一個(gè)setter一個(gè)getter就在這柱宦。
objc_setAssociatedObject
objc_setAssociatedObject(self, @selector(NoAutoSizing), @(NoAutoSizing), OBJC_ASSOCIATION_RETAIN);
方法總共四個(gè)參數(shù)些椒,分別是綁定目標(biāo),關(guān)鍵字掸刊,綁定者免糕,策略
。
所以說簡(jiǎn)單了痒给,綁定目標(biāo)说墨,就是給誰(shuí)綁定骏全,當(dāng)然是UITableViewCell這個(gè)category了苍柏,所以self。
綁定的關(guān)鍵字就是說我給這個(gè)對(duì)象綁定一個(gè)屬性姜贡,我總要有一個(gè)標(biāo)示符去表示那個(gè)屬性吧试吁,這樣我要調(diào)用這個(gè)屬性的時(shí)候通過標(biāo)示符去尋找才能找到這個(gè)屬性。
綁定者楼咳,就是我們要為這個(gè)屬性綁定的值了熄捍。
綁定策略就是說綁定的這個(gè)屬性的引用機(jī)制了。這里要說明一點(diǎn)母怜,這個(gè)綁定策略如何選擇余耽,老司機(jī)目前也沒有搞懂
,所以策略這里都沿用了原作者的寫法苹熏,等老司機(jī)搞懂了之后會(huì)告訴大家的碟贾。
objc_getAssociatedObject
兩個(gè)參數(shù),一個(gè)綁定目標(biāo)轨域,一個(gè)關(guān)鍵字袱耽,通過關(guān)鍵字從綁定目標(biāo)中獲取屬性的值。
這下是不是明白這兩個(gè)setter干发、getter方法的意義了朱巨。
好了,這個(gè)category講完了枉长,他的東西真的很少冀续。
什么琼讽,你敲不出來這兩個(gè)方法?
忘了講了洪唐,你沒引入頭文件跨琳。。桐罕。
import <objc/runtime.h>
HeightCache
為什么說這個(gè)類呢脉让?怎么還不進(jìn)入正題呢?說好的UITableView的category呢功炮?
因?yàn)檫@個(gè)類是負(fù)責(zé)存儲(chǔ)Cell高度
的類溅潜,而UITableView得category只是為獲取cell高度提供了一個(gè)接口,當(dāng)我們移動(dòng)cell薪伏,添加cell滚澜,刪除cell的時(shí)候要對(duì)這個(gè)高度的對(duì)應(yīng)關(guān)系作出很多的操作,UITableView的category中大量的使用了這里的方法
嫁怀,所以老司機(jī)決定先把難啃的骨頭解決了设捐。
在.h中添加三個(gè)字典
@property (strong ,nonatomic)NSMutableDictionary * dicHeightCacheV;//豎直行高緩存字典
@property (strong ,nonatomic)NSMutableDictionary * dicHeightCacheH;//水平行高緩存字典
@property (strong ,nonatomic)NSMutableDictionary * dicHeightCurrent;//當(dāng)前狀態(tài)行高緩存字典(中間量)
為什么三個(gè)字典呢?老司機(jī)是這樣考慮的塘淑,橫屏和豎屏
情況下同樣內(nèi)容的cell有可能是不同的萝招,如果以同一個(gè)高度去取得話有可能出現(xiàn)高度不準(zhǔn)確的問題。所以豎屏橫屏分別一個(gè)字典存捺。那這個(gè)current又是什么呢槐沼?就是自動(dòng)返回當(dāng)前屏幕狀態(tài)所對(duì)應(yīng)的字典
那么一個(gè)中間量,這樣我們寫代碼的時(shí)候可以不用考慮當(dāng)前屏幕狀態(tài)而統(tǒng)一使用current這個(gè)字典
捌治,減少很多代碼量岗钩。
去.m中看看是如何實(shí)現(xiàn)的。
#pragma mark ---懶加載---
-(NSMutableDictionary *)dicHeightCacheH
{
if (!_dicHeightCacheH) {
_dicHeightCacheH = [NSMutableDictionary dictionary];
}
return _dicHeightCacheH;
}
-(NSMutableDictionary *)dicHeightCacheV
{
if (!_dicHeightCacheV) {
_dicHeightCacheV = [NSMutableDictionary dictionary];
}
return _dicHeightCacheV;
}
-(NSMutableDictionary *)dicHeightCurrent//根據(jù)系統(tǒng)狀態(tài)返回對(duì)應(yīng)字典
{
return UIDeviceOrientationIsPortrait([UIDevice currentDevice].orientation)?self.dicHeightCacheV:self.dicHeightCacheH;
}
上面兩個(gè)字典是懶加載肖油,不多說了兼吓,在需要的時(shí)候創(chuàng)建字典。
重點(diǎn)是這個(gè)current森枪。
UIDeviceOrientationIsPortrait()
這個(gè)方法是判斷括號(hào)中的狀態(tài)是否是豎屏狀態(tài)的一個(gè)方法视搏,所以括號(hào)里面我們給他當(dāng)前屏幕狀態(tài)他就可以判斷是不是豎屏了。
[UIDevice currentDevice].orientation而這句代碼見名知意疲恢,取出當(dāng)前設(shè)備的屏幕狀態(tài)
凶朗。
讓后通過三目運(yùn)算符
返回相應(yīng)的字典。
是不是寫法上很簡(jiǎn)單显拳,實(shí)際使用過程中也很方便棚愤。
上面幾個(gè)工具方法最主要的主要由如下幾個(gè)方法,交換兩個(gè)cell高度
的值,插入一個(gè)cell高度
的值宛畦,刪除一個(gè)cell高度
的值瘸洛。通過這三個(gè)最基本的方法組合出所有cell操作需要用到的方法。算法都很基礎(chǔ)次和,沒什么需要說的反肋。
UITableView的category
最后的主角來了。其實(shí)你會(huì)發(fā)現(xiàn)這里的方法并不多踏施。因?yàn)橹皇窍蛲饨缣峁┝瞬迦胧幔瑒h除,刪除全部畅形,移動(dòng)养距,計(jì)算高度五個(gè)接口。
我們看一下大體思路:
老司機(jī)覺得自己畫這圖也是沒誰(shuí)了日熬。棍厌。。
.h中添加一個(gè)屬性竖席,是我們剛才用來存儲(chǔ)高度
的那個(gè)類的一個(gè)實(shí)例耘纱。
@property (strong ,nonatomic)HeightCache * cache;//緩存實(shí)例
其實(shí)你完全可以寫在.m里當(dāng)做一個(gè)私有變量去處理
,這樣也更安全
一些毕荐。
老司機(jī)寫在這里是為了調(diào)試的時(shí)候更直觀
的看到緩存高度操作時(shí)的狀態(tài)束析。實(shí)際應(yīng)用中,如無特殊需要东跪,建議將其寫在.m中
畸陡。
.m中,我們先看一下這幾個(gè)工具方法虽填,這才是核心部分。接口方法都是簡(jiǎn)單調(diào)用這幾個(gè)工具方法曹动,供外界調(diào)用的斋日。
- -(__kindof UITableViewCell *)DW_CalculateCellWithIdentifier:(NSString *)identifier
- -(CGFloat)DW_CalculateCellHeightWithCell:(UITableViewCell *)cell
- -(CGFloat)DW_CalCulateCellWithIdentifier:(NSString *)identifier configuration:(void(^)(id cell))configuration
核心算法都在這了,我一定會(huì)好好解析的墓陈。(第一個(gè)方法返回值有一個(gè)*號(hào)我敲不出來恶守,不知道m(xù)arkdown什么鬼沖突。贡必。)
-(__kindof UITableViewCell *)DW_CalculateCellWithIdentifier:(NSString *)identifier
第一個(gè)方法如下:
///從重用池中返回計(jì)算用的cell
-(__kindof UITableViewCell *)DW_CalculateCellWithIdentifier:(NSString *)identifier
{
if (!identifier.length) {
return nil;
}
NSMutableDictionary <NSString * ,UITableViewCell *> *DicForTheUniqueCalCell = objc_getAssociatedObject(self, _cmd);//利用runtime取出tableV綁定的存有cell的字典
if (!DicForTheUniqueCalCell) {
DicForTheUniqueCalCell = [NSMutableDictionary dictionary];//如果取不到則新建并綁定
objc_setAssociatedObject(self, _cmd, DicForTheUniqueCalCell, OBJC_ASSOCIATION_RETAIN_NONATOMIC);//動(dòng)態(tài)綁定(綁定目標(biāo)兔港,關(guān)鍵字,綁定者仔拟,策略)
}
//以上只是為了只綁定一個(gè)字典衫樊,類比懶加載
UITableViewCell * cell = DicForTheUniqueCalCell[identifier];
if (!cell) {
cell = [self dequeueReusableCellWithIdentifier:identifier];//從重用池中取一個(gè)cell用來計(jì)算,必須以本方式從重用池中取,若以indexPath方式取由于-heightForRowAtIndexPath方法會(huì)造成循環(huán)科侈。
cell.contentView.translatesAutoresizingMaskIntoConstraints = NO;//開啟約束
cell.JustForCal = YES;//標(biāo)記只用來計(jì)算
DicForTheUniqueCalCell[identifier] = cell;
}
//同上载佳,保證只有一個(gè)用來計(jì)算的cell
return cell;
}
其實(shí)每一句注釋都表述的很清楚。不過老司機(jī)還是會(huì)一句一句給你說的臀栈,畢竟這才是老司機(jī)的風(fēng)格蔫慧,恩,就是墨跡
权薯。
恩姑躲,老司機(jī)先說一說重用的問題吧。
我們都喜歡用tableView盟蚣,因?yàn)樗芎玫奶嫖覀冏隽藘?nèi)存控制的問題肋联。
他又是通過什么控制了內(nèi)存呢?節(jié)省了性能呢刁俭?通過重用橄仍。
這些大家都知道。但是有很多孩子誤會(huì)了重用啊牍戚,孩子你們不懂重用啊侮繁。
知道咋回事的這地方跳過吧。
前方高能預(yù)警
如孝,以下內(nèi)容很基礎(chǔ)
宪哩,真的很基礎(chǔ)
,真的真的很基礎(chǔ)
第晰,只是給一些真的不知道的人看的锁孟。
當(dāng)一個(gè)cell將要離開屏幕時(shí),這個(gè)cell會(huì)進(jìn)入重用池茁瘦。重用池并不是什么特殊的東西品抽,就是系統(tǒng)給他放在一邊了。他只是單純的放在一邊了甜熔,不進(jìn)行任何操作圆恤。
當(dāng)一個(gè)cell將要進(jìn)入屏幕的時(shí)候,會(huì)調(diào)用tableView:(UITableView *)tableView cellForRowAtIndexPath這個(gè)代理腔稀,執(zhí)行其中的方法
說這兩句為了說明什么呢盆昙?
第一句我想說明的是,他只是放在重用池了焊虏,沒有進(jìn)行任淡喜!何!操诵闭!作炼团!
重點(diǎn)在哪呢?重點(diǎn)就在于存儲(chǔ)的是整個(gè)cell,包括cell原有的和你添加的所有子視圖
们镜。
第二句我想說明的是币叹,他會(huì)執(zhí)行代理中的每!一模狭!句颈抚!話!
重點(diǎn)在哪呢嚼鹉?你從重用池中取出的cell贩汉,他是會(huì)對(duì)cell進(jìn)行再次進(jìn)行繪制
。
這兩句說明了什么锚赤?
- cell上不要布置太多的控件匹舞,不然存入重用池也夠你吃一壺的。
- tableView:(UITableView *)tableView cellForRowAtIndexPath這里面不要添加子視圖线脚,不然下次取出同一個(gè)cell的時(shí)候還會(huì)在同樣的位置再添加一個(gè)同樣的子視圖赐稽。你上下來回滑動(dòng)的時(shí)候cell就會(huì)添加無數(shù)個(gè)子視圖。
- 沒有什么會(huì)影響重新繪制的浑侥,記住那句代碼一定會(huì)走姊舵,只要走就一定會(huì)繪制。如果說你繪制出了什么問題寓落,不要怪重用括丁,跟他沒關(guān)。問題一定在別的地方伶选。
恩史飞,這是老司機(jī)對(duì)重用的理解。
剛才跳過的旅客朋友們仰税,跳到這可以接上了构资。
接下來我們開始說這個(gè)方法。
為什么我們要取到這個(gè)cell呢肖卧?而不是隨便一個(gè)cell呢蚯窥?
因?yàn)槲覀儫o法保證或者指定只使用一種cell,很多情況下我們是自定義的cell塞帐。這樣的話每個(gè)不同種類的cell上的子視圖是不相同的,在自動(dòng)計(jì)算高度的時(shí)候?qū)ell的布局有很高要求巍沙,所以我們一定要保證我們計(jì)算用的cell與展示用的cell是同一種cell
葵姥。
所以說我們這個(gè)方法只有一個(gè)參數(shù)
,identifier句携。因?yàn)樗菑闹赜贸刂腥〕鯿ell的唯一必要參數(shù)榔幸。
首先為了安全,先判斷傳入的identifier是否為空
,若為空返回nil削咆。只是為了安全牍疏。還有代碼的嚴(yán)謹(jǐn)性
。老司機(jī)又吹牛逼了拨齐,還代碼嚴(yán)謹(jǐn)性鳞陨,我的代碼通常都考慮不周全的。瞻惋。厦滤。
然后是通過runtime從綁定的屬性中取出一個(gè)字典
。如果取到的這個(gè)字典為空則創(chuàng)建一個(gè)字典并綁定歼狼。
為什么要?jiǎng)?chuàng)建一個(gè)字典呢掏导,因?yàn)?code>我們要保證只取到這一個(gè)cell。這個(gè)cell是為了干什么的呢羽峰,就是為了計(jì)算高度的趟咆,那么我每次計(jì)算高度的時(shí)候只要有這么一個(gè)cell
就好了,不要去初始化太多根本不用于顯示只用于計(jì)算的cell梅屉。
然后從字典中取出我們的cell
值纱,如果取出的cell為空,則從重用池中取出一個(gè)cell履植。并存入字典计雌。
首先字典和cell的判空,都是針對(duì)第一次計(jì)算cell高度的時(shí)候來的玫霎。再次進(jìn)入的時(shí)候都不會(huì)為空凿滤。
必須要說明的是,從重用池中取出cell的方法我們一定
要使用dequeueReusableCellWithIdentifier:
這種方式去取庶近,不能以dequeueReusableCellWithIdentifier:indexPath那種方式去取翁脆。
就像老司機(jī)注釋中說的一樣,若以indexPath那種方式去取會(huì)造成雞生蛋蛋生雞的問題鼻种,你這程序就進(jìn)入死循環(huán)了反番。
然后cell.contentView.translatesAutoresizingMaskIntoConstraints = NO;這句是做什么呢?事實(shí)上叉钥,有兩種自動(dòng)布局方式罢缸,autoResizing和autoLayout。
autoResizing是UIView的固有屬性
投队。是在IOS6之前用來實(shí)現(xiàn)自動(dòng)布局的屬性枫疆。當(dāng)然IOS6之后的autoLayout就要比他強(qiáng)大不少了。
事實(shí)上這個(gè)屬性默認(rèn)情況下是YES
敷鸦。當(dāng)為YES時(shí)息楔,則我們設(shè)置約束是無效
的寝贡。因?yàn)楹罄m(xù)我們要手動(dòng)添加一個(gè)約束輔助我們計(jì)算
,所以這里我們將其設(shè)為NO
值依。
然后將計(jì)算標(biāo)識(shí)符置真圃泡,標(biāo)識(shí)這個(gè)cell只參與計(jì)算高度,不負(fù)責(zé)展示愿险。以后遇到批量處理cell的時(shí)候可以判斷這個(gè)標(biāo)識(shí)符颇蜡,讓其不參與運(yùn)算。當(dāng)然老司機(jī)這里只是留了一個(gè)接口拯啦,實(shí)際我們有對(duì)其進(jìn)行處理
澡匪。
通過這個(gè)方法,我們就成功的拿到了一個(gè)計(jì)算高度用的cell褒链。
-(CGFloat)DW_CalculateCellHeightWithCell:(UITableViewCell *)cell
第二個(gè)方法:
///根據(jù)cell計(jì)算cell的高度
-(CGFloat)DW_CalculateCellHeightWithCell:(UITableViewCell *)cell
{
CGFloat width = self.bounds.size.width;
//根據(jù)輔助視圖校正width
if (cell.accessoryView) {
width -= cell.accessoryView.bounds.size.width + 16;
}
else
{
static const CGFloat accessoryWidth[] = {
[UITableViewCellAccessoryNone] = 0,
[UITableViewCellAccessoryDisclosureIndicator] = 34,
[UITableViewCellAccessoryDetailDisclosureButton] = 68,
[UITableViewCellAccessoryCheckmark] = 40,
[UITableViewCellAccessoryDetailButton] = 48
};
width -= accessoryWidth[cell.accessoryType];
}
CGFloat height = 0;
if (!cell.NoAutoSizing && width > 0) {//如果不是非自適應(yīng)模式則添加約束后計(jì)算約束后高度
NSLayoutConstraint * widthConstraint = [NSLayoutConstraint constraintWithItem:cell.contentView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:width];//創(chuàng)建約束
[cell.contentView addConstraint:widthConstraint];//添加約束
height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;//計(jì)算高度
[cell.contentView removeConstraint:widthConstraint];//移除約束
}
if (height == 0) {//如果約束錯(cuò)誤可能導(dǎo)致計(jì)算結(jié)果為零唁情,則以自適應(yīng)模式再次計(jì)算
height = [cell sizeThatFits:CGSizeMake(width, 0)].height;
}
if (height == 0) {//如果計(jì)算仍然為0,則給出默認(rèn)高度
height = 44;
}
if (self.separatorStyle != UITableViewCellSeparatorStyleNone) {//如果不為無分割線模式則添加分割線高度
height += 1.0 /[UIScreen mainScreen].scale;
}
return height;
}
首先我們要想計(jì)算出我們cell的高度甫匹,就需要拿到cell的contentView的實(shí)際寬度
甸鸟。
1.先拿到cell的寬度
2.根據(jù)輔助視圖樣式校正寬度
如果有自定義輔助視圖,則按照自定義輔助視圖的寬度去校正兵迅,如果沒有按照系統(tǒng)輔助視圖樣式去校正寬度抢韭,然后根據(jù)寬度計(jì)算高度
。
3.然后如果是使用autoLayout進(jìn)行自適應(yīng)計(jì)算
恍箭。
這個(gè)也是老司機(jī)為之后留下的接口刻恭,可以控制是否進(jìn)行一autoLayout進(jìn)行計(jì)算。但實(shí)際并沒有處理扯夭。
這里是添加約束的寫法鳍贾,先添加一個(gè)寬度約束
,然后讓系統(tǒng)根據(jù)寬度約束自動(dòng)計(jì)算高度
交洗,接著去掉我們添加的約束
骑科。
4.如果根據(jù)約束計(jì)算結(jié)果錯(cuò)誤,則以sizeThatFits去計(jì)算高度
构拳。
5.如果計(jì)算結(jié)果仍然為零咆爽,則給出默認(rèn)值44
。
6.判斷當(dāng)前tableView的分割線樣式置森,如果有分割線斗埂,還要校正高度
。
好了凫海,至此你已經(jīng)計(jì)算出這個(gè)cell應(yīng)該有的高度了蜜笤。
-(CGFloat)DW_CalCulateCellWithIdentifier:(NSString *)identifier configuration:(void(^)(id cell))configuration
最后一個(gè)方法:
///根據(jù)重用表示取出cell并操作cell后,計(jì)算高度
-(CGFloat)DW_CalCulateCellWithIdentifier:(NSString *)identifier
configuration:(void(^)(id cell))configuration
{
if (!identifier.length) {
return 0;
}
UITableViewCell * cell = [self DW_CalculateCellWithIdentifier:identifier];
[cell prepareForReuse];//放回重用池
if (configuration) {
configuration(cell);//對(duì)cell進(jìn)行操作
}
回重用池
return [self DW_CalculateCellHeightWithCell:cell];
}
這個(gè)方法就比較簡(jiǎn)單了盐碱,先判斷重用標(biāo)示把兔。如果空,直接返回0瓮顽。還是只為了安全县好。
再通過第一個(gè)方法取出cell
,然后將它放回重用池
暖混,以至于下次我們還能取出來這個(gè)cell
缕贡。
不要在意我之后還要對(duì)cell進(jìn)行操作,這個(gè)重用池只是一個(gè)概念
拣播,其實(shí)并不是什么東西晾咪,只是標(biāo)志著這里面的cell可以用于重用,你完全可以理解成他只是cell的一個(gè)標(biāo)簽贮配。所以我之后還是可以繼續(xù)使用這個(gè)cell谍倦。不要糾結(jié)重用池、取出泪勒、放回了昼蛀,少年,他只是一個(gè)概念圆存。
哦對(duì)了叼旋,另外有一點(diǎn)你要注意,你記不記得老司機(jī)說過沦辙,進(jìn)入重用池夫植,是將整個(gè)cell存儲(chǔ)下來,并沒有做其他任何操作
油讯。
其實(shí)你可以重寫prepareForReuse這個(gè)方法
详民,這里可以做任何你想做的事。比如清除所有子視圖撞羽。不過有三點(diǎn)你需要注意:
- 別忘了先寫[super prepareForReuse];
- 真清除所有子視圖的時(shí)候記得別把contentView也刪了
- 重寫之后上面的程序中你要合理的考慮一下[cell prepareForReuse]這句話的位置阐斜,反正這么跟你講,我是沒想出來放哪诀紊。谒出。
最后就是返回高度了。終于完事了邻奠。
留給外界的接口
-(CGFloat)DW_CalculateCellWithIdentifier:(NSString *)identifier indexPath:(NSIndexPath *)indexPath configuration:(void(^)(id cell))configuration
我只說一個(gè)笤喳,這是最重要的一個(gè)返回高度的接口了,如果每次我們都計(jì)算高度碌宴,那我們這寫法也算是廢了杀狡,充其量算一個(gè)自動(dòng)返回高度的算法。
所以我們的邏輯應(yīng)該是先從cache里面中找贰镣,如果沒有呜象,計(jì)算并存儲(chǔ)
膳凝。下次再找這個(gè)indexPath的時(shí)候就能找到了,正如下面的代碼一樣恭陡。
-(CGFloat)DW_CalculateCellWithIdentifier:(NSString *)identifier
indexPath:(NSIndexPath *)indexPath
configuration:(void(^)(id cell))configuration
{
if(self.bounds.size.width != 0)//防止初始寬度為0(如autoLayout初次加載時(shí))
{
if (!identifier.length || !indexPath) {//非空判斷
return 0;
}
NSString * key = [self.cache makeKeyWithIdentifier:identifier indexPath:indexPath];//制作key
if ([self.cache existInCacheByKey:key]) {//如果key存在
return [self.cache heightFromCacheWithKey:key];//從字典中取出高
}
CGFloat height = [self DW_CalCulateCellWithIdentifier:identifier configuration:configuration];//不存在則計(jì)算高度
[self.cache cacheHeight:height byKey:key];//并緩存
return height;
}
return 0;
}
最后關(guān)于如何使用
就是在原來返回tableView高度的方法出調(diào)用上面那個(gè)方法蹬音,僅此而已。
特別注意休玩,一定要在方法中先填充數(shù)據(jù)著淆,一定要在方法中先填充數(shù)據(jù),一定要在方法中先填充數(shù)據(jù)
拴疤。重要的事情說三遍永部。否則你永遠(yuǎn)都是44啊親們。呐矾。
我知道今天這個(gè)教程看上去很抽象苔埋,所以這次我會(huì)附上demo的鏈接。
不過老司機(jī)還是想說一下自己對(duì)demo這件事的看法凫佛。
老司機(jī)能選擇在這里分享一些自己學(xué)到的東西讲坎,自然就不是一個(gè)敝帚自珍的人。然而之所以不愛附上demo鏈接是因?yàn)槔纤緳C(jī)覺得每次我都已經(jīng)很詳細(xì)的在博客中貼出我全部代碼而且一句一句講解真的已經(jīng)知無不言言無不盡了愧薛,我覺得編程這種東西還是得下手敲一遍晨炕,看別人的東西看一天也看不懂。所以我更提倡你們自己去敲一遍毫炉。如果我把demo鏈接一放出來瓮栗,你們直接下載了就去看,就去改瞄勾,真的沒有自己敲一遍學(xué)的快费奸。當(dāng)然有同學(xué)實(shí)在有需要可以留下郵箱,老司機(jī)會(huì)給你單獨(dú)發(fā)demo的进陡。
常用套話了愿阐,這么貪幕虛榮的老司機(jī)不就圖你點(diǎn)個(gè)喜歡么=。=趾疚,覺得好點(diǎn)個(gè)喜歡吧缨历。
轉(zhuǎn)載記得附上鏈接。
http://www.reibang.com/p/2b192257276f