若是是一名 iOS 開發者, 用 Objective-C,設計模式
·····bash
平時給 tableView , collectionView 添加的 mj_footer , mj_header ,都是 MJRefreshComponent 的子類。網絡
下拉刷新的框架設計,先搞定基礎服務,就是 MJRefreshComponent 了。框架
MJRefreshComponent 本質上是一個 UIView, 添加了一個父視圖對象 UIScrollView。而後須要把咱們用的 mj_footer , mj_header 添加到父視圖對象 scrollView 上面。 那個 scrollView 就是咱們業務代碼裏面的 tableView , collectionView .函數
要想實現咱們的下拉刷新,上拉加載, 就須要實現相關基礎設施。對用戶下拉和上拉做出反饋,MJRefreshComponent 採用的是觀察三個屬性。scrollView 的 contentOffset 和 contentSize ,scrollView 自帶的平移手勢 panGestureRecognizer 的 state.ui
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
atom
經過 KVO,把這三個屬性轉化爲三個方法,spa
// 偏移量改變
- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change{}
// 內容大小改變
- (void)scrollViewContentSizeDidChange:(NSDictionary *)change{}
// 拖拽狀態發生改變
- (void)scrollViewPanStateDidChange:(NSDictionary *)change{}
複製代碼
Header 只調用 scrollViewContentOffsetDidChange:
, Footer 調用上面三個方法。 Header 只關注 偏移量, Footer 都要關注。設計
至關於 MJ 使用 KVO 生造了三個相似系統 scrollView 的代理方法,出來了。代理
MJRefresh 處理的至關有節操, 直接採用 KVO , 不涉及任何的 scrollView 的系統代理方法。 很是的框架。不打擾業務代碼的可能實現。
MJRefreshComponent 裏面有一個內部方法 - (void)executeRefreshingCallback
, 處理咱們傳入的事件,業務上通常是網絡請求方法,上拉刷新第一頁的數據,下拉加載下一頁的數據。
能夠檢測到用戶的操做了, 就要做出響應,顯示 UI ,出現一朵菊花,執行咱們的業務代碼(做爲外部傳入給框架的)。
KVO 檢測到的 scrollView 的偏移量 contentOffset, 內容大小 contentSize 是不斷變化的,一觸發, 啪啪啪, 一打數據甩過去了。
咱們須要的是,在合適的時機,調用一次方法就行了。
MJRefreshComponent 採用的是狀態管理。
/** 刷新控件的狀態 */
typedef NS_ENUM(NSInteger, MJRefreshState) {
MJRefreshStateIdle = 1, /** 普通閒置狀態 */
MJRefreshStatePulling, /** 鬆開就能夠進行刷新的狀態 */
MJRefreshStateRefreshing, /** 正在刷新中的狀態 */
MJRefreshStateWillRefresh, /** 即將刷新的狀態 */
MJRefreshStateNoMoreData /** 全部數據加載完畢,沒有更多的數據了 */
};
複製代碼
把監測到的 scrollView 的偏移量和內容大小的變化,轉化爲狀態的改變,把連續的數據變化轉化爲離散的一兩次操做,挺不錯的。具體在子類頭部刷新(下拉刷新)MJRefreshHeader 和尾巴刷新(上拉加載)MJRefreshFooter 中實現。
檢測到用戶的列表滾動狀況了,能夠轉化爲操做了,就要顯示UI, 放在 MJRefreshComponent 的子類頭部/尾部類中實現了。而後就是執行開發者傳入的方法。
這個內部方法 - (void)executeRefreshingCallback
,提供兩種實現,一種是匿名函數,self.refreshingBlock();
, 另外一種是 target - action, MJ 封裝了一個運行時的發消息方法 MJRefreshMsgSend(MJRefreshMsgTarget(self.refreshingTarget), self.refreshingAction, self);
MJRefresh 在繼承上使用的比較出彩,MJRefreshComponent 創建基礎設施,MJRefreshHeader 完成了下拉加載的功能,MJRefreshStateHeader 添加了基本的樣式(主要是文本標籤 UILabel , 更新的時間文本, 狀態文本), MJRefreshNormalHeader 添加了箭頭和菊花(活動指示器,UIActivityIndicatorView )
具體上拉加載和下拉刷新的 UI 部分,MJRefreshComponent 提供了大體以下四個空方法實現。
/** 擺放子控件frame */
- (void)placeSubviews NS_REQUIRES_SUPER;
/** 當scrollView的contentOffset發生改變的時候調用 */
- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change NS_REQUIRES_SUPER;
/** 當scrollView的contentSize發生改變的時候調用 */
- (void)scrollViewContentSizeDidChange:(NSDictionary *)change NS_REQUIRES_SUPER;
/** 當scrollView的拖拽狀態發生改變的時候調用 */
- (void)scrollViewPanStateDidChange:(NSDictionary *)change NS_REQUIRES_SUPER;
複製代碼
MJRefresh 在繼承上有兩步幹得漂亮, 從 MJRefreshComponent 到 Header 和 Footer , 這裏有一個區分。
還有就是 MJMJRefresh 有不少的樣式,比較有特點的是上拉刷新部分, 會回彈到底部的默認的 footer , 會回彈到底部的帶動圖的 footer , 會自動刷新的默認的 footer ,會自動刷新的帶動圖的 footer.
MJRefreshComponent 在內存管理上,也有優勢,
@interface MJRefreshComponent: UIView
{
/** 父控件 */
__weak UIScrollView * _scrollView;
}
/** 父控件 */
@property (weak, nonatomic, readonly) UIScrollView *scrollView;
複製代碼
父視圖 scrollView,咱們的表視圖,格子視圖,外部 readonly 屬性調用, 內部的 _scrollView 成員變量修改。
- (void)setMj_footer:(MJRefreshFooter *)mj_footer{
if (mj_footer != self.mj_footer) {
// 刪除舊的,添加新的
[self.mj_footer removeFromSuperview];
[self insertSubview:mj_footer atIndex:0];
// 存儲新的
objc_setAssociatedObject(self, &MJRefreshFooterKey, mj_footer, OBJC_ASSOCIATION_RETAIN);
}
}
- (MJRefreshFooter *)mj_footer{
return objc_getAssociatedObject(self, &MJRefreshFooterKey);
}
複製代碼
這一點上,NSHipster 的對象關聯 Associated Objects 講得很不錯。
MJRefresh 對設計模式中繼承, 觀察者的使用, 使用繼承的設計模式,分層加功能, 對 Runtime 的使用,對宏的大量使用, 都挺精彩的。