MJRefresh源碼閱讀

可改進部分

  • 在 MJRefreshComponent.h 的 34 行,

typedef void (^MJRefreshComponentbeginRefreshingCompletionBlock)( );

這里的 begin 應(yīng)當(dāng)遵循命名規(guī)則改為 Begin ,與下面的

typedef void (^MJRefreshComponentEndRefreshingCompletionBlock)( );

統(tǒng)一風(fēng)格,可能是作者一時手誤吧.

  • 在 MJRefreshComponent.m 的 154 行,

    // 預(yù)發(fā)當(dāng)前正在刷新中時調(diào)用本方法使得header insert回置失敗

這里應(yīng)改為 預(yù)防




源碼部分

來自MJRefresh作者官方源碼的結(jié)構(gòu)圖
來自MJRefresh作者官方源碼的結(jié)構(gòu)圖
  • MJRefreshConst.h

    該文件中自定義了輸出方法

    #ifdef DEBUG
    #define MJRefreshLog(...) NSLog(__VA_ARGS__)
    #else
    #define MJRefreshLog(...)
    #endif
    

    我在他的基礎(chǔ)上寫了個自定義的輸出方法,多了輸出文件名,方法,行數(shù)的功能

    #define XZDEBUG
    #ifdef XZDEBUG
    #define TestLog(fmt, ...)    \
    NSLog((@"\nFile : %@\nmethod : %s\nLine : %zd\n" fmt),   \
    [[NSString stringWithFormat:@"%s",__FILE__] lastPathComponent], \
    __FUNCTION__, __LINE__, ##__VA_ARGS__);
    
    #else
    #define TestLog(fmt, ...)
    #endif
    
  • UIScrollView+MJRefresh.h && UIScrollView+MJRefresh.m

    在這兩個文件里,動態(tài)的給 UIScrollView 加上了 mj_header , mj_footer ,和 mj_reloadDataBlock 屬性.

    • 使用
    
    objc_setAssociatedObject(id object, const void *key, id value,   objc_AssociationPolicy policy);
    
    objc_getAssociatedObject(id object, const void *key);
    

    函數(shù)的時候,對于這個參數(shù) key ,其唯一性體現(xiàn)在它的內(nèi)存地址唯一而不是這個地址內(nèi)存放的值.

    • UITableViewUICollectionView+ (void)load方法中,運用 method_exchangeImplementations(Method m1, Method m2) 函數(shù)調(diào)換了系統(tǒng)原有的 - (void)reloadData 方法和自行定義的 - (void)mj_reloadData 方法的實現(xiàn).于是有了下面的這段代碼
    
    - (void)mj_reloadData
    {
      [self mj_reloadData];
      [self executeReloadDataBlock];
    }
    
    

    這里在 - (void)mj_reloadData 方法內(nèi)部執(zhí)行 [self mj_reloadData]; ,之所以不會造成死循環(huán)是因為此 時的 - (void)mj_reloadData 實現(xiàn)已經(jīng)變?yōu)橄到y(tǒng)原有方法 - (void)reloadData 的實現(xiàn).

  • MJRefreshComponent.h && MJRefreshComponent.m

    這個文件主要是定義了刷新控件的狀態(tài)以及刷新回調(diào), 其中 MJRefreshComponent 是刷新控件的基類,里面定義了諸多供子類實現(xiàn)的接口.至于代碼方面沒有什么值得記錄的.

  • MJRefreshHeader.m MJRefreshHeader.m

    這個文件 override 了一些父類的方法,我摘抄出一段我理了很久才理清楚的代碼片段(其實主要是因為我對 UIScrollViewcontentInset 不了解導(dǎo)致的).

  - (void)scrollViewContentOffsetDidChange:(NSDictionary *)change
  {
      [super scrollViewContentOffsetDidChange:change];
    
      // 在刷新的refreshing狀態(tài)
      if (self.state == MJRefreshStateRefreshing) {
          if (self.window == nil) return;
        
          // sectionheader停留解決
          CGFloat insetT = - self.scrollView.mj_offsetY > _scrollViewOriginalInset.top ? - self.scrollView.mj_offsetY : _scrollViewOriginalInset.top;
          insetT = insetT > self.mj_h + _scrollViewOriginalInset.top ? self.mj_h + _scrollViewOriginalInset.top : insetT;
          self.scrollView.mj_insetT = insetT;
        
          self.insetTDelta = _scrollViewOriginalInset.top - insetT;
          return;
      }
    
      // 跳轉(zhuǎn)到下一個控制器時接癌,contentInset可能會變
       _scrollViewOriginalInset = self.scrollView.contentInset;
    
      // 當(dāng)前的contentOffset
      CGFloat offsetY = self.scrollView.mj_offsetY;
      // 頭部控件剛好出現(xiàn)的offsetY
      CGFloat happenOffsetY = - self.scrollViewOriginalInset.top;
    
      // 如果是向上滾動到看不見頭部控件,直接返回
      // >= -> >
      if (offsetY > happenOffsetY) return;
    
      // 普通 和 即將刷新 的臨界點
      CGFloat normal2pullingOffsetY = happenOffsetY - self.mj_h;
      CGFloat pullingPercent = (happenOffsetY - offsetY) / self.mj_h;
    
      if (self.scrollView.isDragging) { // 如果正在拖拽
          self.pullingPercent = pullingPercent;
          if (self.state == MJRefreshStateIdle && offsetY < normal2pullingOffsetY) {
              // 轉(zhuǎn)為即將刷新狀態(tài)
              self.state = MJRefreshStatePulling;
          } else if (self.state == MJRefreshStatePulling && offsetY >= normal2pullingOffsetY) {
              // 轉(zhuǎn)為普通狀態(tài)
              self.state = MJRefreshStateIdle;
          }
      } else if (self.state == MJRefreshStatePulling) {// 即將刷新 && 手松開
          // 開始刷新
          [self beginRefreshing];
      } else if (pullingPercent < 1) {
          self.pullingPercent = pullingPercent;
      }
  }

代碼的邏輯是當(dāng)這個方法被調(diào)用的時候,

  1. 如果此時處于刷新狀態(tài),那么根據(jù) scrollViewcontentOffset 來調(diào)整 scrollViewcontentInset ,即做到了上下滑動時,能讓 mj_header 的這個控件懸涂鬯希或者隱藏
  2. 如果此時不處于刷新狀態(tài),那么根據(jù) scrollViewcontentOffsetisDragging 實現(xiàn) mj_headerstate 狀態(tài)切換,通過
> \- (void)setState:(MJRefreshState)state;

方法,實現(xiàn)了改變 `scrollView` 的 `contentInset ` 等屬性.
  • MJRefreshStateHeader.h && MJRefreshStateHeader.m

代碼層面來說,這個類沒什么難的,主要就是給顯示刷新狀態(tài)和時間的 UILabel 做了下布局和根據(jù)不同情況顯示不同文字.
不過有趣的一點是在頭文件中,對 lastUpdatedTimeLabelstateLabel 采取的是 weak 的內(nèi)存管理策略(在他的子類中,控件也都是用 weak 的管理策略),所以才會用下面這樣比較 "奇葩" 的方式創(chuàng)建 _stateLabel(lastUpdatedTimeLabel同理).其實我這里有點不懂,這里完全可以用 strong 的內(nèi)存管理策略,為什么非要用 weak ,希望有自己看法的童靴可以來指點下.

  - (UILabel *)stateLabel
  {
      if (!_stateLabel) {
          [self addSubview:_stateLabel = [UILabel mj_label]];
      }
      return _stateLabel;
  }

通過 selflabel 的強引用保證其不會被釋放,然后才能實現(xiàn) stateLabel 不會為 nil.

  • MJRefreshNormalHeader.h && MJRefreshNormalHeader.m

這個類相對于他的父類 MJRefreshStateHeadermj_header 加上了一個箭頭圖標(biāo)和一個 UIActivityIndicatorView (即那個旋轉(zhuǎn)的小菊花),代碼層面上也沒什么需要記錄的點.

  • MJRefreshGifHeader.h && MJRefreshGifHeader.m

這個類相對于他的父類 MJRefreshStateHeadermj_header 添加了一個顯示動圖的 UIImageView ,可以通過公開的接口來自行配置顯示的圖片組和動畫時長.代碼方面沒有需要記錄的點.

  • MJRefreshFooter.h && MJRefreshFooter.m

暴露出了 endRefreshingWithNoMoreData 的接口以及重置沒有更多數(shù)據(jù)狀態(tài)的接口 resetNoMoreData .代碼方面沒什么值得記錄的.

  • MJRefreshBackFooter.h && MJRefreshBackFooter.m

  • MJRefreshBackStateFooter.h && MJRefreshBackStateFooter.m

  • MJRefreshBackNormalFooter.h && MJRefreshBackNormalFooter.m

  • MJRefreshBackGifFooter.h && MJRefreshBackGifFooter.m

header 相對應(yīng)的,代碼方面和 header 類似.

  • MJRefreshAutoFooter.h && MJRefreshAutoFooter.m

  • MJRefreshAutoStateFooter.h && MJRefreshAutoStateFooter.m

  • MJRefreshAutoNormalFooter.h && MJRefreshAutoNormalFooter.m

  • MJRefreshAutoGifFooter.h && MJRefreshAutoGifFooter.m

這部分的代碼和 MJRefreshBackxxx 對應(yīng)的代碼類似,只不過這里實現(xiàn)了根據(jù)設(shè)置的下拉百分比來自動刷新的功能.

總結(jié)

MJRefresh 這個框架主要通過對 UIScrollViewcontentOffsetcontentSize 進行觀察監(jiān)測,實時改變 UIScrollViewcontentInsetUIScrollVieworigin.y ,并且根據(jù)當(dāng)時的 offset 的高度對應(yīng)關(guān)系和 dragging 屬性來判斷是否要更改控件的 state 狀態(tài),進而判斷是否要刷新還是要結(jié)束刷新.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缺猛,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子椭符,更是在濱河造成了極大的恐慌荔燎,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件销钝,死亡現(xiàn)場離奇詭異有咨,居然都是意外死亡,警方通過查閱死者的電腦和手機蒸健,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門摔吏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人纵装,你說我怎么就攤上這事征讲。” “怎么了橡娄?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵诗箍,是天一觀的道長。 經(jīng)常有香客問我挽唉,道長滤祖,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任瓶籽,我火速辦了婚禮匠童,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘塑顺。我一直安慰自己汤求,他們只是感情好俏险,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著扬绪,像睡著了一般竖独。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上挤牛,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天莹痢,我揣著相機與錄音,去河邊找鬼墓赴。 笑死竞膳,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的诫硕。 我是一名探鬼主播顶猜,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼痘括!你這毒婦竟也來了长窄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤纲菌,失蹤者是張志新(化名)和其女友劉穎挠日,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體翰舌,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡嚣潜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了椅贱。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片懂算。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖庇麦,靈堂內(nèi)的尸體忽然破棺而出计技,到底是詐尸還是另有隱情,我是刑警寧澤山橄,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布垮媒,位于F島的核電站,受9級特大地震影響航棱,放射性物質(zhì)發(fā)生泄漏睡雇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一饮醇、第九天 我趴在偏房一處隱蔽的房頂上張望它抱。 院中可真熱鬧,春花似錦朴艰、人聲如沸观蓄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蜘腌。三九已至沫屡,卻和暖如春饵隙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背金矛。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工芯急, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人驶俊。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓娶耍,卻偏偏與公主長得像,于是被迫代替她去往敵國和親饼酿。 傳聞我的和親對象是個殘疾皇子榕酒,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

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