1、Runtime
1.1 關(guān)聯(lián)對(duì)象
該框架為UIScrollView添加了兩個(gè)“成員變量”,header和footer,這是在分類中實(shí)現(xiàn)的。因?yàn)槭墙oUIScrollView及其子類UITableView和UICollectionView添加,不能通過繼承實(shí)現(xiàn)向其添加header和footer。所以作者采用了分類的方法。
但是我們通常會(huì)把成員變量放在類聲明的頭文件里,或者放在類實(shí)現(xiàn)的@implementation 前面。我們不能在分類中添加成員變量,編譯器會(huì)報(bào)錯(cuò)。Objective-C針對(duì)這一問題,提供了一種解決方案:關(guān)聯(lián)對(duì)象(Associated Object)。其定義是這樣的:
/**
* Sets an associated value for a given object using a given key and association policy.
*
* @param object The source object for the association.
* @param key The key for the association.
* @param value The value to associate with the key key for object. Pass nil to clear an existing association.
* @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
*
* @see objc_setAssociatedObject
* @see objc_removeAssociatedObjects
*/
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
我們可以給對(duì)象關(guān)聯(lián)很多其他對(duì)象,通過const void *key來區(qū)分,是一個(gè)唯一的指針,并且還有相應(yīng)的內(nèi)存管理策略,并且有對(duì)應(yīng)等效的@property屬性(當(dāng)該關(guān)聯(lián)對(duì)象成為屬性時(shí))。
作者在此時(shí)用到的就是這個(gè)辦法,
static const char MJRefreshHeaderKey = '\0';
- (void)setHeader:(MJRefreshHeader *)header
{
if (header != self.header) {
// 刪除舊的,添加新的
[self.header removeFromSuperview];
[self addSubview:header];
// 存儲(chǔ)新的
[self willChangeValueForKey:@"header"]; // KVO
// 添加關(guān)聯(lián)對(duì)象
objc_setAssociatedObject(self, &MJRefreshHeaderKey,
header, OBJC_ASSOCIATION_ASSIGN);
[self didChangeValueForKey:@"header"]; // KVO
}
}
- (MJRefreshHeader *)header
{
// 取出關(guān)聯(lián)對(duì)象
return objc_getAssociatedObject(self, &MJRefreshHeaderKey);
}
1.2 self 和 super
該框架定義子控件的Frame,調(diào)用的是- (void)layoutSubviews;,但是閱讀框架的時(shí)候發(fā)現(xiàn)只有MJRefreshComponent 實(shí)現(xiàn)了這個(gè)方法:
- (void)layoutSubviews
{
[super layoutSubviews];
[self placeSubviews];
}
其他子類調(diào)用的就是這個(gè)方法,這就要區(qū)分一下self 和 super 的區(qū)別了。
這個(gè)文檔講的不錯(cuò) http://www.cocoachina.com/ios/20141224/10740.html 。
1.3 方法交換
//當(dāng)類加載到內(nèi)存的時(shí)候,調(diào)用
+ (void)load
{
[self exchangeInstanceMethod1:@selector(reloadData) method2:@selector(mj_reloadData)];
}
+ (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));
}
2 header
該框架最基礎(chǔ)的類是MJRefreshComponent,包含了header 和 footer 共有的屬性和方法,包括刷新狀態(tài)控制方法,初始化等共有方法。
創(chuàng)建header的方法作者提供了兩個(gè):
/** 創(chuàng)建header */
+ (instancetype)headerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock;
/** 創(chuàng)建header */
+ (instancetype)headerWithRefreshingTarget:(id)target refreshingAction:(SEL)action;
初始化最終是調(diào)用父類的方法:
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
// 準(zhǔn)備工作
[self prepare]; //哪個(gè)對(duì)象調(diào)用,就用該對(duì)象isa指向的類中的方法。
// 默認(rèn)是普通狀態(tài)
self.state = MJRefreshStateIdle;
}
return self;
}
之后,當(dāng)header添加到父視圖上時(shí),調(diào)用了- (void)willMoveToSuperview:(UIView *)newSuperview;方法,設(shè)置一些屬性,添加監(jiān)聽事件(KVO),監(jiān)聽父視圖的滾動(dòng)。
其中最關(guān)鍵的方法是- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change;,監(jiān)聽contentOffset的變化,來判斷header相應(yīng)的state 。
根據(jù)MJRefreshHeader這個(gè)類,繼承它得到了MJRefreshStateHeader(里面包含了提示文字和最后刷新時(shí)間),MJRefreshNormalHeader(里面有活動(dòng)指示器),MJRefreshGifHeader (圖片),當(dāng)然我們也可以繼承MJRefreshHeader來創(chuàng)造我們自己需要的下拉刷新視圖,擴(kuò)展性強(qiáng)。