研究了一下MJRefresh
框架, 整體的結構大概是這樣的, mj_header和mj_footer都繼承自一個基類MJRefreshComponent
, 這個基類繼承自UIView, mj_header和mj_footer又都有本身的子類, 子類來實現具體的方法.git
框架中還有一個UIScrollView的分類UIScrollView+MJRefresh
. 這個分類是一個比較簡單的分類, 包含三個屬性:mj_header, mj_footer, mj_reloadDataBlock, 和一個方法- (NSInteger)mj_totalDataCount
, 這個方法能夠用來獲取UITableView和UICollectionView的包含數據的條目數量.github
#1. MJRefresh學習筆記web
以下代碼所示, 實現的功能是, 交換兩個方法的實現, 利用runtime的原理.框架
@implementation NSObject (MJRefresh) // 交換兩個實例方法的實現 + (void)exchangeInstanceMethod1:(SEL)method1 method2:(SEL)method2 { method_exchangeImplementations(class_getInstanceMethod(self, method1), class_getInstanceMethod(self, method2)); } // 交換兩個類方法的實現 + (void)exchangeClassMethod1:(SEL)method1 method2:(SEL)method2 { method_exchangeImplementations(class_getClassMethod(self, method1), class_getClassMethod(self, method2)); } @end
- (NSInteger)mj_totalDataCount { NSInteger totalCount = 0; if ([self isKindOfClass:[UITableView class]]) { UITableView *tableView = (UITableView *)self; for (NSInteger section = 0; section<tableView.numberOfSections; section++) { totalCount += [tableView numberOfRowsInSection:section]; } } else if ([self isKindOfClass:[UICollectionView class]]) { UICollectionView *collectionView = (UICollectionView *)self; for (NSInteger section = 0; section<collectionView.numberOfSections; section++) { totalCount += [collectionView numberOfItemsInSection:section]; } } return totalCount; }
#pragma mark - header static const char MJRefreshHeaderKey = '\0'; - (void)setMj_header:(MJRefreshHeader *)mj_header { if (mj_header != self.mj_header) { // 刪除舊的,添加新的 // 當添加新的頭刷新控件時候, 先移除舊的頭部刷新控件 [self.mj_header removeFromSuperview]; // 插入新的頭部刷新控件,, 使用insertSubview:atIndex:, 將控件插入到指定的位置, 方便後面去查找這個控件, 擺放控件的位置. [self insertSubview:mj_header atIndex:0]; // 存儲新的 // 使用關聯(association)來給對象動態的添加一個關聯對象. willChangeValueForKey: 和 didChangeValueForKey: 是爲了觸發KVO, 可能在其它地方監聽了這個關聯的變化, 以從新佈局呀, 從新刷新呀之類的工做. [self willChangeValueForKey:@"mj_header"]; // KVO objc_setAssociatedObject(self, &MJRefreshHeaderKey, mj_header, OBJC_ASSOCIATION_ASSIGN); [self didChangeValueForKey:@"mj_header"]; // KVO } } - (MJRefreshHeader *)mj_header { // association的get方法, 根據實例對象和key獲取一個關聯對象. return objc_getAssociatedObject(self, &MJRefreshHeaderKey); }
規則的三目運算符是 [表達式1]?[表達式2]:[表達式3], 其它語言沒有測試, oc中也能夠這麼用: [表達式1]? : [表達式3]佈局
!self.mj_reloadDataBlock ? : self.mj_reloadDataBlock(self.mj_totalDataCount);
@implementation UICollectionView (MJRefresh) // load是一個初始化方法, 在類剛剛開始初始化時候調用, 在類剛開始初始化時候調用這個方法, 交換兩個方法的實現 + (void)load { [self exchangeInstanceMethod1:@selector(reloadData) method2:@selector(mj_reloadData)]; } // load時候交換了這兩個方法的實現, 因此在這個方法裏面能夠調用本身, 看起來是調用本身, 其實調用了UITableView的reloadData方法 // 這裏的邏輯是這樣的, 咱們刷新tableView數據時候調用reloadData, 實際上會調用到mj_reloadData方法, 這個方法的實現調用mj_reloadData實際上會調用到系統的reloadData方法, 而後執行 UIScrollView的executeReloadDataBlock方法, 來執行一個block代碼塊 - (void)mj_reloadData { [self mj_reloadData]; [self executeReloadDataBlock]; } @end
MJRefresh中使用的一個宏定義: // 運行時objc_msgSend #define MJRefreshMsgSend(...) ((void (*)(void *, SEL, UIView *))objc_msgSend)(__VA_ARGS__) #define MJRefreshMsgTarget(target) (__bridge void *)(target) 使用: /** * 其中 self.refreshingTarget表示要執行的方法所在的對象,id類型; self.refreshingAction表示要執行的方法,SEL類型; self表示當前的view. * 簡單來講就是執行target的action方法. */ MJRefreshMsgSend(MJRefreshMsgTarget(self.refreshingTarget), self.refreshingAction, self)
經過scrollView的這個屬性能夠判斷出來scrollView當前是否是正在被拖拽.學習
self.scrollView.isDragging
- (void)example31 { __unsafe_unretained UIWebView *webView = self.webView; webView.delegate = self; __unsafe_unretained UIScrollView *scrollView = self.webView.scrollView; // 添加下拉刷新控件 scrollView.mj_header= [MJRefreshNormalHeader headerWithRefreshingBlock:^{ [webView reload]; }]; // 若是是上拉刷新,就以此類推 }
/** * 描述: 狀態欄樣式 * * @return 返回狀態欄的樣式 */ - (UIStatusBarStyle)preferredStatusBarStyle { return self.statusBarStyle; } /** * 描述: 狀態欄是否隱藏 * * @return 返回狀態欄是否隱藏 */ - (BOOL)prefersStatusBarHidden { return self.statusBarHidden; } /** * setter方法 * * @param statusBarHidden 是否隱藏的setter方法 */ - (void)setStatusBarHidden:(BOOL)statusBarHidden { _statusBarHidden = statusBarHidden; [self setNeedsStatusBarAppearanceUpdate]; } /** * setter方法 * * @param statusBarStyle 狀態欄樣式的setter方法 */ - (void)setStatusBarStyle:(UIStatusBarStyle)statusBarStyle { _statusBarStyle = statusBarStyle; [self setNeedsStatusBarAppearanceUpdate]; }
上下拉刷新的實現原理測試
上下拉刷新的實現仍是比較簡單的. 下拉刷新的header, 實際上, header的位置的y座標是負值, 這就致使tableView中正常狀況下是不會顯示出來header的, 當下拉時候, header就會被展現出來, 當下拉到必定程度, 超過header的height時候, 再鬆手會立刻改變tableView的inset並從新放header的位置, 切換上面的數據, endRefreshing時候, 會從新改變tableviw的inset, 執行動畫將header隱藏掉. 上拉加載的原理和這個如出一轍.動畫
MJRefresh第一次閱讀設計
這個上下拉刷新的框架, 目前在github上有七千多的star, 對中國區的iOS開發者來講, 是一個很不錯的框架. 閱讀起來也是沒有太大難度的, 可是經過閱讀這個開源框架仍是學到了不少的東西.code
首先, 經過繼承來封裝一個框架, 可讓框架業務邏輯層級分明, 更易於維護. 之前我也有了解過模板方法模式
, 簡單說就是經過繼承實現複用, 今天閱讀完MJRefresh
框架, 我想對模板方法模式又有了新的認識和理解. 在框架的設計和封裝時候, 咱們能夠在基礎的父類中實現最基本的業務邏輯, 定義抽象類, 而後讓子類去處理方法的實現. 若是方法的實現很是的複雜, 能夠利用相似MJRefresh中, 處於不一樣層次的子類實現本身不一樣的業務, 而後再去調用父類的方法, 這樣達到具體業務處理和邏輯處理的分離, 讓代碼更加便於維護和閱讀.
其次, 封裝一個框架時候注意事項. 我的認爲: 1.儘量的下降程序每一個部分的耦合度. 2.要寫易於擴展和維護的代碼. 3.無論程序內部實現多麼複雜, 拋出來的接口必定要儘量的簡單.
其次閱讀優秀框架, 可以很好的提高我的的水平, 也能提高程序猿們重造輪子的能力, 是不 ?