前言
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_EXTERN
和 extern
到底有什么不同呢?
我们点击发现跳入了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+MJExtension
和UIScrollView+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,但是如果设置了不为CGRectZero
的frame
的时候就会触发;
3.addSubview:
时会触发;
4.view
的frame
发生改变时会触发;
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
是不能响应事件的,需要将该覆盖在上面的view
的userInteractionEnabled
属性设为NO
才行。
动画配置属性(UIViewAnimationOptions):
在动画执行过程中,默认所有的子视图都是禁止用户交互的,但是你可以通过设置动画的配置属性为UIViewAnimationOptionAllowUserInteraction
来允许在UIView
动画时响应用户交互,使得UIView
在执行动画期间依然能够响应用户事件。
事实上,我们可以给UIView
动画的配置属性进行很多配置,详情见:UIViewAnimationOptions类型