第三方庫之-MJRefresh

MJRefresh類的關係圖


研究了一下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

1.1 利用objc/runtime交換方法的實現

以下代碼所示, 實現的功能是, 交換兩個方法的實現, 利用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

1.2 獲取UITableView和UICollectionView數據的數量

- (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;
}

1.3 mj_header的set,get方法 註釋版

#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.4 三目運算符還能夠這麼用

規則的三目運算符是 [表達式1]?[表達式2]:[表達式3], 其它語言沒有測試, oc中也能夠這麼用: [表達式1]? : [表達式3]佈局

!self.mj_reloadDataBlock ? : self.mj_reloadDataBlock(self.mj_totalDataCount);

1.5 MJRefresh中UICollectionView的分類是這麼寫的

@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

1.6 執行某一個對象的一個方法

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)

1.7 UIScrollView的一個屬性:scrollView.isDragging

經過scrollView的這個屬性能夠判斷出來scrollView當前是否是正在被拖拽.學習

self.scrollView.isDragging

1.8 UIWebView下拉刷新

- (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];
    }];
    
    // 若是是上拉刷新,就以此類推
}

1.9 設置狀態欄的樣式和是否隱藏

/**
 *  描述: 狀態欄樣式
 *
 *  @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];
}

2. 談談閱讀MJRefresh框架的感覺

上下拉刷新的實現原理測試

上下拉刷新的實現仍是比較簡單的. 下拉刷新的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.無論程序內部實現多麼複雜, 拋出來的接口必定要儘量的簡單.

其次閱讀優秀框架, 可以很好的提高我的的水平, 也能提高程序猿們重造輪子的能力, 是不 ?

MJRefresh註釋版

相關文章
相關標籤/搜索