MJRefresh源码阅读3——干货整理

前言

MJRefresh源码阅读1——结构梳理
MJRefresh源码阅读2——核心类MJRefreshHeader
前面这两篇已经大概梳理清楚了MJRefresh的实现逻辑,但是里面还有一些知识点仍值得我们好好整理整理,以便以后温习。


UIKIT_EXTERN 和 extern的区别:

MJRefresh控件专门新建了MJRefreshConst文件来在里面定义了需要用到的宏和全局常量。说起全局常量,在《52个有效方法》笔记1——熟悉Objective-C这篇笔记里已经说过了如何定义一个全局常量,即以extern关键字为前缀。但是在MJRefresh中却以UIKIT_EXTERN关键字取代了extern

.h文件中:

UIKIT_EXTERN NSString *const MJRefreshHeaderStateIdleText;

.m文件中:

NSString *const MJRefreshHeaderStateIdleText = @"下拉可以刷新";

UIKIT_EXTERNextern到底有什么不同呢?
我们点击发现跳入了UIKitDefines.h宏定义文件的下面几行代码:

#ifdef __cplusplus
#define UIKIT_EXTERN        extern "C" __attribute__((visibility ("default")))
#else
#define UIKIT_EXTERN            extern __attribute__((visibility ("default")))
#endif

下面是我收集到的一段解释资料:

其中__cplusplus 是cpp中的自定义宏,那么定义了这个宏的话表示这是一段cpp的代码,也就是说,上面的代码的含义是:如果这是一段cpp的代码,那么加入extern"C"和其中的代码。

extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。

那extern "C"呢?C++之父在设计C++之时,考虑到当时已经存在了大量的C代码,为了支持原来的C代码和已经写好C库,需要在C++中尽可能的支持C,而 extern "C"就是其中的一个策略。

attribute是GNU C的一种机制,用法为attribute_ ((attribute-list))。当项目需要作为一个库被外包引用的时候通常在编译时可以用参数-fvisibility指定所有符号的可见性。在编译命令中加入 -fvisibility=hidden参数,会将所有默认的public的属性变为hidden。此时,如果对函数设置attribute((visibility ("default")))参数,使特定的函数仍然按默认的public属性处理,则-fvisibility=hidden参数不会对该函数起作用。所以,设置了-fvisibility=hidden参数之后,只有设置了attribute((visibility ("default")))的函数才是对外可见的。

用一句话说就是,UIKIT_EXTERN而不用extern是因为它兼容C++的代码。


利用Method Swizzing调换方法的实现:

关于Method Swizzing,以前已经记过笔记,不想再赘述。
请查看这篇笔记:《52个有效方法》笔记3——探究runtime


善用分类提供便捷的语法糖:

首先MJRefresh就是以UIScrollView的分类的形式给tableView添加了header,可以说这种以分类对原有类进行扩展的方式非常优雅。但不仅如此,我们发现在源码中还可以看到其他两个分类:UIView+MJExtensionUIScrollView+MJExtension
比如UIView+MJExtension可以很便捷地访问/修改所有UIView的坐标尺寸属性。我自己也写了一遍:

@interface UIView (YWFrame)
@property (nonatomic, assign) CGFloat   yw_x;
@property (nonatomic, assign) CGFloat   yw_y;
@property (nonatomic, assign) CGPoint   yw_origin;
@property (nonatomic, assign) CGFloat   yw_width;
@property (nonatomic, assign) CGFloat   yw_height;
@property (nonatomic, assign) CGSize    yw_size;

// 布局
@property (nonatomic, assign, readonly) CGFloat   yw_top;
@property (nonatomic, assign, readonly) CGFloat   yw_bottom;
@property (nonatomic, assign, readonly) CGFloat   yw_left;
@property (nonatomic, assign, readonly) CGFloat   yw_right;
@end
#import "UIView+YWFrame.h"

@implementation UIView (YWFrame)

- (CGFloat)yw_x
{
    return self.frame.origin.x;
}
- (void)setYw_x:(CGFloat)yw_x
{
    CGRect frame = CGRectZero;
    frame.origin.x = yw_x;
    self.frame = frame;
}

- (CGFloat)yw_y
{
    return self.frame.origin.y;
}
- (void)setYw_y:(CGFloat)yw_y
{
    CGRect frame = CGRectZero;
    frame.origin.y = yw_y;
    self.frame = frame;
}

- (CGPoint)yw_origin
{
    return self.frame.origin;
}
- (void)setYw_origin:(CGPoint)yw_origin
{
    CGRect frame = CGRectZero;
    frame.origin = yw_origin;
    self.frame = frame;
}

- (CGFloat)yw_width
{
    return self.frame.size.width;
}
- (void)setYw_width:(CGFloat)yw_width
{
    CGRect frame = CGRectZero;
    frame.size.width = yw_width;
    self.frame = frame;
}

- (CGFloat)yw_height
{
    return self.frame.size.height;
}
- (void)setYw_height:(CGFloat)yw_height
{
    CGRect frame = CGRectZero;
    frame.size.height = yw_height;
    self.frame = frame;
}

- (CGSize)yw_size
{
    return self.frame.size;
}
- (void)setYw_size:(CGSize)yw_size
{
    CGRect frame = CGRectZero;
    frame.size = yw_size;
    self.frame = frame;
}


- (CGFloat)yw_top
{
    return self.frame.origin.y;
}

- (CGFloat)yw_bottom
{
    return self.yw_top+self.yw_height;
}

- (CGFloat)yw_left
{
    return self.frame.origin.x;
}

- (CGFloat)yw_right
{
    return self.yw_left+self.yw_width;
}
@end

在layoutSubviews方法里调整布局:

MJRefresh源码中header的子视图创建后并未给设置坐标尺寸,而是在layoutSubviews方法里统一设置调整的。
说起layoutSubviews方法,我们平时比较少用,但是在网上又经常看到它的身影。它究竟是用来干嘛的?它的执行时机是什么?

layoutSubviews的作用:
layoutSubviews是对子视图重新布局。比如,我们想更新子视图的位置的时候,可以通过调用layoutSubviews方法,即可以实现对子视图重新布局。
layoutSubviews默认是不做任何事情的,用到的时候,需要在自类进行重写。

layoutSubviews以下情况会被调用:
1.调用setNeedsLayout方法时会触发执行;
2.初始化不会触发layoutSubviews,但是如果设置了不为CGRectZeroframe的时候就会触发;
3.addSubview:时会触发;
4.viewframe发生改变时会触发;
5.滚动scrollView是会触发;
6.旋转屏幕时会触发。

正如第1种情况,源码中就是在设置子视图的可见性的,重写的setter方法中调用了setNeedsLayout方法。因为当设置header上某子视图隐藏时,同时header上剩下的子视图的布局要发生调整。

- (void)setStateHidden:(BOOL)stateHidden
{
    _stateHidden = stateHidden;
    
    self.stateLabel.hidden = stateHidden;
    [self setNeedsLayout];
}

userInteractionEnabled属性:

简单的说,该属性控制某UIView是否接受并响应用户的交互事件。若为YES,则依照响应链响应事件;若为NO,则代表其不接受交互事件,就像视图层级中没有它一样,UIView会跳过它响应事件。

有两种在开发中经常遇到的事件不响应的情况:

情况一:UIImageView上添加按钮,点击按钮是不会响应事件的,因为UIImageView的这个属性默认是NO,需要将其userInteractionEnabled属性设为YES才行。
情况二:UIButton上覆盖有层透明的view,此时,UIButton是不能响应事件的,需要将该覆盖在上面的viewuserInteractionEnabled属性设为NO才行。


动画配置属性(UIViewAnimationOptions):

在动画执行过程中,默认所有的子视图都是禁止用户交互的,但是你可以通过设置动画的配置属性为UIViewAnimationOptionAllowUserInteraction来允许在UIView动画时响应用户交互,使得UIView在执行动画期间依然能够响应用户事件。

事实上,我们可以给UIView动画的配置属性进行很多配置,详情见:UIViewAnimationOptions类型


日历(NSCalendar):

iOS-时间与日期详解

你可能感兴趣的:(MJRefresh源码阅读3——干货整理)