UITableView+FDTemplateLayoutCell 源碼探究

gqq_110 8年前發布 | 15K 次閱讀 iOS開發 移動開發 UITableView

  • 在我們日常的業務中,常常伴隨大量的UITableView,然而動態地計算Cell的高度常常困擾著我。自從使用了這個組件之后,一切都變得沒那么復雜。所以深入學習下這個框架的組件的實現原理。
  • 框架地址: https://github.com/forkingdog/UITableView-FDTemplateLayoutCell

代碼文件目錄

- UITableView+FDIndexPathHeightCache.h

  • UITableView+FDIndexPathHeightCache.m
  • UITableView+FDKeyedHeightCache.h
  • UITableView+FDKeyedHeightCache.m
  • UITableView+FDTemplateLayoutCell.h
  • UITableView+FDTemplateLayoutCell.m
  • UITableView+FDTemplateLayoutCellDebug.h
  • UITableView+FDTemplateLayoutCellDebug.m </code></pre>

    首先,介紹一下這幾個類的基本功能,再層層推進,逐一分析。

    - UITableView+FDIndexPathHeightCache,主要負責cell通過NSIndexPath進行緩存高度的功能
  • UITableView+FDKeyedHeightCache,主要負責cell通過key值進行緩存高度的功能
  • UITableView+FDTemplateLayoutCell,提供接口方法方便用戶定義cell的數據源,以及幫助我們計算cell的高度
  • UITableView+FDTemplateLayoutCellDebug,提供一些Debug打印信息 </code></pre>

    關于這個框架,坦白說,從代碼中看,作者無疑秀了一波runtime底層的功底,讓我這種小白起初一臉懵逼。自然我得換種思路來解讀這個框架,那就是從字數最少的類入手吧。

    UITableView+FDTemplateLayoutCellDebug

    @interface UITableView (FDTemplateLayoutCellDebug)
     
    //設置Debug模式是否打開
    @property (nonatomic, assign) BOOL fd_debugLogEnabled;
     
    //通過該方法,傳遞NSLog打印對應的Debug信息
  • (void)fd_debugLog:(NSString *)message;   @end </code></pre>
    @implementationUITableView (FDTemplateLayoutCellDebug)
     
  • (BOOL)fd_debugLogEnabled {     return [objc_getAssociatedObject(self, _cmd) boolValue]; }  
  • (void)setFd_debugLogEnabled:(BOOL)debugLogEnabled {     objc_setAssociatedObject(self, @selector(fd_debugLogEnabled), @(debugLogEnabled), OBJC_ASSOCIATION_RETAIN); }  
  • (void)fd_debugLog:(NSString *)message {     if (self.fd_debugLogEnabled) {         NSLog(@" FDTemplateLayoutCell %@", message);     } }   @end </code></pre>
    • 在分類中,如果要聲明屬性,可以通過使用關聯度對象( AssociatedObject ), 通過objc_setAssociatedObject() 添加屬性,objc_getAssociatedObject() 獲取屬性。實際上,相當于在運行時系統中動態地在內存中開辟一塊空間,存儲debugLogEnabled這個BOOL變量,類似懶加載的方式,通過runtime實現setter & getter方法。
    • 關于runtime的知識點,推薦這篇博客: http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/

    UITableView+FDKeyedHeightCache

    #import
     
    @interface FDKeyedHeightCache : NSObject
     
    //判斷緩存中是否存在key為值的緩存高度
  • (BOOL)existsHeightForKey:(id)key;   //對指定key的cell設置高度為height
  • (void)cacheHeight:(CGFloat)heightbyKey:(id)key;   //從緩存中獲取對應key的cell的高度height值
  • (CGFloat)heightForKey:(id)key;   //從緩存中刪除指定key的cell的值
  • (void)invalidateHeightForKey:(id)key;   //移除緩存中所有的cell的高度緩存值
  • (void)invalidateAllHeightCache; @end   @interface UITableView (FDKeyedHeightCache) @property (nonatomic, strong, readonly) FDKeyedHeightCache fd_keyedHeightCache; @end </code></pre>
    • 先來看看FDKeyedHeightCache類中聲明的屬性
    @property (nonatomic, strong) NSMutableDictionary, NSNumber > mutableHeightsByKeyForPortrait;
     
    @property (nonatomic, strong) NSMutableDictionary, NSNumber > mutableHeightsByKeyForLandscape;
    </code></pre> 
    

    不難看出,這是兩個指定泛型的可變字典。

    • mutableHeightsByKeyForPortrait : 用于緩存設備豎直放置時,對應key的cell的高度值。
    • mutableHeightsByKeyForLandscape : 用于緩存設備橫向放置時,對應key的cell的高度值。
    • FDKeyedHeightCache中的接口方法
    - (BOOL)existsHeightForKey:(id)key {
        NSNumber number = self.mutableHeightsByKeyForCurrentOrientation[key];
        return number && ![numberisEqualToNumber:@-1];
    }
     
  • (void)cacheHeight:(CGFloat)heightbyKey:(id)key {     self.mutableHeightsByKeyForCurrentOrientation[key] = @(height); }  
  • (CGFloat)heightForKey:(id)key { #if CGFLOAT_IS_DOUBLE     return [self.mutableHeightsByKeyForCurrentOrientation[key] doubleValue]; #else     return [self.mutableHeightsByKeyForCurrentOrientation[key] floatValue]; #endif }  
  • (void)invalidateHeightForKey:(id)key {     [self.mutableHeightsByKeyForPortraitremoveObjectForKey:key];     [self.mutableHeightsByKeyForLandscaperemoveObjectForKey:key]; }  
  • (void)invalidateAllHeightCache {     [self.mutableHeightsByKeyForPortraitremoveAllObjects];     [self.mutableHeightsByKeyForLandscaperemoveAllObjects]; } </code></pre>
    • 這些方法并不晦澀,看到這里,大家不禁會問,self.mutableHeightsByKeyForCurrentOrientation從何而來,這也是我覺得這個類中,細節處理比較好的地方,由于此處考慮到緩存的高度區別了設備方向,所以框架作者,通過一個getter方法來獲取對應的存放高度的字典。
    - (NSMutableDictionary, NSNumber *> *)mutableHeightsByKeyForCurrentOrientation {
        return UIDeviceOrientationIsPortrait([UIDevicecurrentDevice].orientation) ? self.mutableHeightsByKeyForPortrait: self.mutableHeightsByKeyForLandscape;
    }
    
    • 根據UIDeviceOrientationIsPortrait()函數,傳入當前設備的放置方向([UIDevice currentDevice].orientation

      )進行判斷。從而便可以通過屬性簡潔判斷需要從那個字典中取值了。

    UITableView+FDIndexPathHeightCache

    @interface FDIndexPathHeightCache : NSObject
     
    // 如果您使用索引路徑獲取高度緩存,則自動啟用
    @property (nonatomic, assign) BOOL automaticallyInvalidateEnabled;
     
    // Height cache
  • (BOOL)existsHeightAtIndexPath:(NSIndexPath *)indexPath;
  • (void)cacheHeight:(CGFloat)heightbyIndexPath:(NSIndexPath *)indexPath;
  • (CGFloat)heightForIndexPath:(NSIndexPath *)indexPath;
  • (void)invalidateHeightAtIndexPath:(NSIndexPath *)indexPath;
  • (void)invalidateAllHeightCache;   @end   @interface UITableView (FDIndexPathHeightCache) @property (nonatomic, strong, readonly) FDIndexPathHeightCache *fd_indexPathHeightCache; @end   @interface UITableView (FDIndexPathHeightCacheInvalidation) /// 當你不想通過刪除緩存中的高度來刷新數據源重新計算時,可以調用這個方法。 /// 該方法中用過runtime重寫了tableView中修改cell的一些方法,例如插入cell,刪除cell,移動cell,以及reloadData方法。
  • (void)fd_reloadDataWithoutInvalidateIndexPathHeightCache; @end </code></pre>
    • 首先看看FDIndexPathHeightCache中設置的屬性
    typedef NSMutableArray *> FDIndexPathHeightsBySection;
     
    @interface FDIndexPathHeightCache ()
    @property (nonatomic, strong) FDIndexPathHeightsBySection *heightsBySectionForPortrait;
    @property (nonatomic, strong) FDIndexPathHeightsBySection *heightsBySectionForLandscape;
    @end
    

    通過前面key的高度緩存分析,不難猜出這幾個屬性是干什么的。

    • 由于通過NSIndexPath獲取高度緩存,NSIndexPath對應section, 以及indexPath。FDIndexPathHeightsBySection這個數組,通過數組嵌套字典的數據結構來存儲,不同的section組中對應的cell的高度緩存。
    • FDIndexPathHeightCache中的方法

      由于頭文件聲明的幾個接口方法,與FDKeyedHeightCache中的思路類似,就不再費口舌了,大家翻看源碼便一目了然。

    - (void)enumerateAllOrientationsUsingBlock:(void (^)(FDIndexPathHeightsBySection *heightsBySection))block {
        block(self.heightsBySectionForPortrait);
        block(self.heightsBySectionForLandscape);
    }
     
  • (void)invalidateHeightAtIndexPath:(NSIndexPath )indexPath {     [self buildCachesAtIndexPathsIfNeeded:@[indexPath]];     [self enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection heightsBySection {         heightsBySection[indexPath.section][indexPath.row] = @-1;     }]; }  
  • (void)invalidateAllHeightCache {     [self enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {         [heightsBySectionremoveAllObjects];     }]; }  
  • (void)buildCachesAtIndexPathsIfNeeded:(NSArray )indexPaths {     // Build every section array or row array which is smaller than given index path.     [indexPathsenumerateObjectsUsingBlock:^(NSIndexPath indexPath, NSUIntegeridx, BOOL *stop) {         [self buildSectionsIfNeeded:indexPath.section];         [self buildRowsIfNeeded:indexPath.rowinExistSection:indexPath.section];     }]; }  
  • (void)buildSectionsIfNeeded:(NSInteger)targetSection {     [self enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {         for (NSIntegersection = 0; section = heightsBySection.count) {                 heightsBySection[section] = [NSMutableArrayarray];             }         }     }]; }  
  • (void)buildRowsIfNeeded:(NSInteger)targetRowinExistSection:(NSInteger)section {     [self enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection heightsBySection) {         NSMutableArray heightsByRow = heightsBySection[section];         for (NSIntegerrow = 0; row = heightsByRow.count) {                 heightsByRow[row] = @-1;             }         }     }]; } </code></pre>
    • 這幾個封裝的方法,主要一點就是通過block來回調,判斷刪除NSIndexPath對應的cell高度緩存。
    • 在這個類中,最核心的莫過于UITableView (FDIndexPathHeightCacheInvalidation) 這個分類的實現細節,廢話少說,繼續看代碼。
    //我們只是轉發主調用,在崩潰報告中,最頂層的方法在堆棧可能存在FD,
    //但它真的不是我們的錯誤,你應該檢查你的表視圖的數據源和
    //重新加載時顯示單元格不匹配。
    static void __FD_TEMPLATE_LAYOUT_CELL_PRIMARY_CALL_IF_CRASH_NOT_OUR_BUG__(void (^callout)(void)) {
        callout();
    }
    #define FDPrimaryCall(...) do {__FD_TEMPLATE_LAYOUT_CELL_PRIMARY_CALL_IF_CRASH_NOT_OUR_BUG__(^{__VA_ARGS__});} while(0)
    
    • 調用的接口方法
    - (void)fd_reloadDataWithoutInvalidateIndexPathHeightCache {
        FDPrimaryCall([self fd_reloadData];);
    }
    
    • 這個方法,主要調用的是[self fd_reloadData],看到這里的時候,我們的第一反應應該是此處通過runtime 交換了系統方法的實現。這是一種動態的攔截技巧,也算是基礎的runtime知識了,懵逼的小伙伴可以認真閱讀下前面提到的關于runtime的大牛博文。
    • 既然如此,先來看看作者重寫了哪些系統的方法吧。
    + (void)load {
        // All methods that trigger height cache's invalidation
        SEL selectors[] = {
            @selector(reloadData),
            @selector(insertSections:withRowAnimation:),
            @selector(deleteSections:withRowAnimation:),
            @selector(reloadSections:withRowAnimation:),
            @selector(moveSection:toSection:),
            @selector(insertRowsAtIndexPaths:withRowAnimation:),
            @selector(deleteRowsAtIndexPaths:withRowAnimation:),
            @selector(reloadRowsAtIndexPaths:withRowAnimation:),
            @selector(moveRowAtIndexPath:toIndexPath:)
        };
     
        for (NSUInteger index = 0; index < sizeof(selectors) / sizeof(SEL); ++index) {
            SEL originalSelector = selectors[index];
            SEL swizzledSelector = NSSelectorFromString([@"fd_"stringByAppendingString:NSStringFromSelector(originalSelector)]);
            Method originalMethod = class_getInstanceMethod(self, originalSelector);
            Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    }
    
    • 通過method_exchangeImplementations() C函數, 將重寫的方法,一一交換成重寫的方法。
    • 在這些fd_方法中的實現細節中,需要注意的一點就是,如果對應的fd_indexPathHeightCache設置了automaticallyInvalidateEnabled屬性為YES時,對應的方法對高度緩存做相應的處理,重新更新fd_indexPathHeightCache中存儲的高度緩存。
    • 當第一次reloadData,或者cell的行數發生變化(增減行,section) ,會先在tableview不處于滾動狀態的時候異步計算那些沒有被計算過的cell的高度,做預緩存,這個想法非常贊。
    • 使用者需要小心,這些調用是異步的, tableview delegate有可能會在預緩存計算的時候不存在了,導致程序崩潰,所以使用者在tableview需要析構的時候,在對應的tableview controller的dealloc中講self.tableview.delegate = nil;,確保delegate后續不會是一個野指針。

    UITableView+FDTemplateLayoutCell

    至此,我們已經分析了幾個子類的實現邏輯,唯一剩下一個分類,也是我們使用這個框架的入口 FDTemplateLayoutCell 分類。全面了解這個組件近在咫尺。

    @interface UITableView (FDTemplateLayoutCell)
     
    /* 為給定的重用標識符訪問內部模板布局單元格。
  • 一般來說,你不需要知道這些模板布局單元格。
  • @param identifier重用必須注冊的單元格的標識符。 */
  • (__kindofUITableViewCell )fd_templateCellForReuseIdentifier:(NSString )identifier;   /* 返回由重用標識符指定并配置的類型的單元格的高度, 并通過block來配置。
  • 單元格將被放置在固定寬度,垂直擴展的基礎上,相對于其動態內容,使用自動布局。
  • 因此,這些必要的單元被設置為自適應,即其內容總是確定它的寬度給定的寬度等于tableview的寬度。
  • @param identifier用于檢索和維護模板的字符串標識符cell通過系統方法
  • '- dequeueReusableCellWithIdentifier:'
  • @param configuration用于配置和提供內容的可選塊到模板單元格。
  • 配置應該是最小的滾動性能足以計算單元格的高度。 */
  • (CGFloat)fd_heightForCellWithIdentifier:(NSString )identifierconfiguration:(void (^)(idcell))configuration;   / 計算的高度將通過其索引路徑進行高速緩存,當需要時返回高速緩存的高度,因此,可以節省大量額外的高度計算。
  • 無需擔心數據源更改時使緩存高度無效,它將在調用“-reloadData”或任何觸發方法時自動完成UITableView的重新加載。
  • @param indexPath此單元格的高度緩存所屬的位置。 */
  • (CGFloat)fd_heightForCellWithIdentifier:(NSString )identifiercacheByIndexPath:(NSIndexPath )indexPathconfiguration:(void (^)(idcell))configuration;   /* 此方法通過模型實體的標識符緩存高度。
  • 如果你的模型改變,調用“-invalidateHeightForKey:(id )key”到無效緩存并重新計算,它比“cacheByIndexPath”方便得多。
  • @param key model entity的標識符,其數據配置一個單元格。 */
  • (CGFloat)fd_heightForCellWithIdentifier:(NSString )identifiercacheByKey:(id)keyconfiguration:(void (^)(idcell))configuration;   @end   @interface UITableView (FDTemplateLayoutHeaderFooterView)   / 返回在具有重用標識符的表視圖中注冊的Header或Footer視圖的高度。
  • 在調用“ - [UITableView registerNib / Class:forHeaderFooterViewReuseIdentifier]”之后使用它,
  • 與“-fd_heightForCellWithIdentifier:configuration:”相同。
  • 它將調用“-sizeThatFits:”
  • UITableViewHeaderFooterView的子類不使用自動布局。 */
  • (CGFloat)fd_heightForHeaderFooterViewWithIdentifier:(NSString )identifierconfiguration:(void (^)(idheaderFooterView))configuration;   @end   @interface UITableViewCell (FDTemplateLayoutCell) / 指示這是僅用于計算的模板布局單元格。
  • 當配置單元格時,如果有非UI的副作用,你可能需要這個。
  • 類似:    - (void)configureCell:(FooCell )cell atIndexPath:(NSIndexPath )indexPath {       cell.entity = [self entityAtIndexPath:indexPath];        if (!cell.fd_isTemplateLayoutCell) {           [self notifySomething]; // non-UI side effects        }   } / @property (nonatomic, assign) BOOL fd_isTemplateLayoutCell;   / 啟用以強制此模板布局單元格使用“框架布局”而不是“自動布局”,
  • 并且通過調用“-sizeThatFits:”來詢問單元格的高度,所以你必須重寫這個方法。
  • 僅當要手動控制此模板布局單元格的高度時才使用此屬性
  • 計算模式,默認為NO。 / @property (nonatomic, assign) BOOL fd_enforceFrameLayout;   @end </code></pre>
    • 先來看看我們平時開發中最頻繁調用的兩個方法
    • (CGFloat)fd_heightForCellWithIdentifier:(NSString )identifier cacheByIndexPath:(NSIndexPath )indexPath configuration:(void (^)(id cell))configuration;
    • (CGFloat)fd_heightForCellWithIdentifier:(NSString )identifier cacheByKey:(id)key configuration:(void (^)(id cell))configuration;</li> </ul>
      - (CGFloat)fd_heightForCellWithIdentifier:(NSString )identifiercacheByIndexPath:(NSIndexPath )indexPathconfiguration:(void (^)(idcell))configuration {
          if (!identifier || !indexPath) {
              return 0;
          }
       
          // Hit cache
          if ([self.fd_indexPathHeightCacheexistsHeightAtIndexPath:indexPath]) {
              return [self.fd_indexPathHeightCacheheightForIndexPath:indexPath];
          }
       
          CGFloatheight = [self fd_heightForCellWithIdentifier:identifierconfiguration:configuration];
          [self.fd_indexPathHeightCachecacheHeight:heightbyIndexPath:indexPath];
       
          return height;
      }
       
    • (CGFloat)fd_heightForCellWithIdentifier:(NSString )identifiercacheByKey:(id)keyconfiguration:(void (^)(idcell))configuration {     if (!identifier || !key) {         return 0;     }       // Hit cache     if ([self.fd_keyedHeightCacheexistsHeightForKey:key]) {         CGFloatcachedHeight = [self.fd_keyedHeightCacheheightForKey:key];         return cachedHeight;     }       CGFloatheight = [self fd_heightForCellWithIdentifier:identifierconfiguration:configuration];     [self.fd_keyedHeightCachecacheHeight:heightbyKey:key];     return height; } </code></pre>
      • 這兩個方法,分別是對cell通過NSIndexPath 或者 key值 進行高度緩存,讀取高度的時候,先從緩存cache中讀取,如果緩存中沒有,在通過[self fd_heightForCellWithIdentifier:identifier configuration:configuration]方法進行計算高度并加入緩存中。
      - (CGFloat)fd_heightForCellWithIdentifier:(NSString )identifierconfiguration:(void (^)(idcell))configuration {
          if (!identifier) {
              return 0;
          }
       
          UITableViewCell templateLayoutCell = [self fd_templateCellForReuseIdentifier:identifier];
       
          //手動調用以確保與實際單元格的一致行為。 (顯示在屏幕上)
          [templateLayoutCellprepareForReuse];
       
          if (configuration) {
              configuration(templateLayoutCell);
          }
       
          return [self fd_systemFittingHeightForConfiguratedCell:templateLayoutCell];
      }
      </code></pre> 
      
      • 通過blocks進行配置并計算cell的高度,主要通過[self fd_templateCellForReuseIdentifier:identifier]方法創建一個UITableViewCell的實例templateLayoutCell,最后再把templateLayoutCell放入[self fd_systemFittingHeightForConfiguratedCell:templateLayoutCell]中進行計算返回高度。
      - (__kindofUITableViewCell )fd_templateCellForReuseIdentifier:(NSString )identifier {
          NSAssert(identifier.length > 0, @"Expect a valid identifier - %@", identifier);
       
          NSMutableDictionary templateCellsByIdentifiers = objc_getAssociatedObject(self, _cmd);
          if (!templateCellsByIdentifiers) {
              templateCellsByIdentifiers = @{}.mutableCopy;
              objc_setAssociatedObject(self, _cmd, templateCellsByIdentifiers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
          }
       
          UITableViewCell templateCell = templateCellsByIdentifiers[identifier];
       
          if (!templateCell) {
              templateCell = [self dequeueReusableCellWithIdentifier:identifier];
              NSAssert(templateCell != nil, @"Cell must be registered to table view for identifier - %@", identifier);
              templateCell.fd_isTemplateLayoutCell = YES;
              templateCell.contentView.translatesAutoresizingMaskIntoConstraints = NO;
              templateCellsByIdentifiers[identifier] = templateCell;
              [self fd_debugLog:[NSStringstringWithFormat:@"layout cell created - %@", identifier]];
          }
       
          return templateCell;
      }
      </code></pre> 
      
      • 將所有創建的templateCell放置在一個字典templateCellsByIdentifiers中,并通過runtime將其加入內存中作為屬性,實際上,templateCell 也是通過identifier在復用隊列中獲取復用的。所以,cell在使用前,應先注冊為cell的復用對象。
      • 最后調用的[self fd_systemFittingHeightForConfiguratedCell:templateLayoutCell]進行高度計算。當然也是最關鍵的一個操作,既然這是一個高度計算的框架,那么計算的步驟當然是重中之重。
      - (CGFloat)fd_systemFittingHeightForConfiguratedCell:(UITableViewCell )cell {
          CGFloatcontentViewWidth = CGRectGetWidth(self.frame);
       
          if (cell.accessoryView) {
              //如果有定制accessoryView,則去除這個寬度
              contentViewWidth -= 16 + CGRectGetWidth(cell.accessoryView.frame);
          } else {
              //如果有系統accessoryView展示,則去除對應的寬度。
              static const CGFloatsystemAccessoryWidths[] = {
                  [UITableViewCellAccessoryNone] = 0,
                  [UITableViewCellAccessoryDisclosureIndicator] = 34,
                  [UITableViewCellAccessoryDetailDisclosureButton] = 68,
                  [UITableViewCellAccessoryCheckmark] = 40,
                  [UITableViewCellAccessoryDetailButton] = 48
              };
              contentViewWidth -= systemAccessoryWidths[cell.accessoryType];
          }
       
          CGFloatfittingHeight = 0;
       
          if (!cell.fd_enforceFrameLayout && contentViewWidth > 0) {
                //如果是自動布局,則將contentView的寬度約束添加進去。
                //這樣做的目的是讓UILabel之類的內容可能多行的控件適應這個寬度折行(當然前提是我們已經設置好了這些控件的布局約束)。然后調用systemLayoutSizeFittingSize來計算高度。
                //最后移除剛才臨時添加的contentView寬度約束。
              NSLayoutConstraint *widthFenceConstraint = [NSLayoutConstraintconstraintWithItem:cell.contentViewattribute:NSLayoutAttributeWidthrelatedBy:NSLayoutRelationEqualtoItem:nilattribute:NSLayoutAttributeNotAnAttributemultiplier:1.0 constant:contentViewWidth];
              [cell.contentViewaddConstraint:widthFenceConstraint];
       
              fittingHeight = [cell.contentViewsystemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
              [cell.contentViewremoveConstraint:widthFenceConstraint];
          }
       
          if (fittingHeight == 0) {
              // 嘗試調用 '- sizeThatFits:'進行高度計算.
              // 注意:配件高度不應包括分隔線視圖高度。
              fittingHeight = [cellsizeThatFits:CGSizeMake(contentViewWidth, 0)].height;
          }
       
          // 進行完前面的邏輯后高度如果仍然為0,則使用默認行高44
          if (fittingHeight == 0) {
              fittingHeight = 44;        
          }
       
          // 添加一像素作為tableView分割線高度。
          if (self.separatorStyle != UITableViewCellSeparatorStyleNone) {
              fittingHeight += 1.0 / [UIScreenmainScreen].scale;
          }
       
          return fittingHeight;
      }
      </code></pre> 

      至此,就大致將這個框架分析的差不多了,源碼中,對類的實例化均為采用runtime添加AssociatedObject的方式。就不做解釋了。

      最后

       

      來自:http://ios.jobbole.com/91697/

       

 本文由用戶 gqq_110 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!