前言
都说程序员想提高技术最好的方法就是读源码,学习大神的源码,吸取大神写代码的逻辑思路,甚至连写代码习惯和风格都可以学习。所以我决定读一些常用第三方库的源码,今天带来一篇读MBProgressHUD源码,抛砖引玉,如果写的有问题希望大家见谅并且希望大家能提出来(ps:第一次写。。。。看到最后有福利哦!!!!)
MBProgressHUD
MBProgressHUD是什么?MBProgressHUD是一个显示HUD窗口的第三方库,常用于网络请求或者后台执行任务时,在界面上显示一个表示进度的loading视图或者文本提示的HUD窗口,给用户提示。
好了,现在介绍完了MBProgressHUD,我们来开始读源码吧,这篇主要通过三个部分来解读它:
- 核心API
- 方法调用流程图
- 内部方法解读
1.核心API
- 类方法
// 创建新的HUD,添加到提供的view上并显示
+ (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated;
//找到最顶层的HUD并且隐藏,发现并删除则返回YES,否则NO
+ (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated;
//找到最顶层的HUD并且返回
+ (nullable MBProgressHUD *)HUDForView:(UIView *)view;
- 实例方法
//初始化创建HUD,并将HUD的frame设置为view.bounds
- (instancetype)initWithView:(UIView *)view;
//显示HUD
- (void)showAnimated:(BOOL)animated;
//隐藏HUD
- (void)hideAnimated:(BOOL)animated;
//延迟指定时间后隐藏HUD
- (void)hideAnimated:(BOOL)animated afterDelay:(NSTimeInterval)delay;
- 重要的属性
//show函数执行到显示HUD的时间段,默认为0
@property (assign, nonatomic) NSTimeInterval graceTime;
//显示的最小时间
@property (assign, nonatomic) NSTimeInterval minShowTime;
//HUD显示的类型
@property (assign, nonatomic) MBProgressHUDMode mode;
//HUD显示动画类型
@property (assign, nonatomic) MBProgressHUDAnimation animationType
看完了主要API,接下来我们来看看MBProgressHUD的方法调用流程图:
2.方法调用流程图
MBProgressHUD的提供的接口方法可以大致上分为两类:显示(show)和隐藏(hide)。而且无论是调用显示方法还是隐藏方法,最终都会走到私有方法animateIn:withType: completion:
里。下面是方法调用流程图:
看完了方法调用流程图,让我们来看看内部方法是怎么实现的吧
3.内部方法解读
我们先来解读show
方法:
+ (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated {
MBProgressHUD *hud = [[self alloc] initWithView:view];
// 创建HUD,之后会调用[self initWithFrame:view.bounds],把HUD的frame设置为传进来的view的frame
hud.removeFromSuperViewOnHide = YES; // 将HUD设置为在隐藏后被移除的状态,应该是一个标记
[view addSubview:hud]; // 把创建的HUD添加到传进来的view上
[hud showAnimated:animated];
return hud;
}
- (void)showAnimated:(BOOL)animated {
MBMainThreadAssert();
[self.minShowTimer invalidate]; // 取消当前设置的minShowTimer
self.useAnimation = animated; //设置是否执行animation
self.finished = NO; // 表示当前任务未完成
// 如果设置了graceTime,则要推迟HUD的显示
if (self.graceTime > 0.0) {
NSTimer *timer = [NSTimer timerWithTimeInterval:self.graceTime target:self selector:@selector(handleGraceTimer:) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
self.graceTimer = timer;
} // 立刻显示HUD
else {
[self showUsingAnimation:self.useAnimation];
}
}
// graceTimer触发的方法
- (void)handleGraceTimer:(NSTimer *)theTimer {
// Show the HUD only if the task is still running
if (!self.hasFinished) {
[self showUsingAnimation:self.useAnimation];
}
}
// 所以的show方法最后都会走到这个方法
- (void)showUsingAnimation:(BOOL)animated {
// Cancel any previous animations
// 移除所有的动画
[self.bezelView.layer removeAllAnimations];
[self.backgroundView.layer removeAllAnimations];
// Cancel any scheduled hideDelayed: calls
// 取消hideDelayTimer
[self.hideDelayTimer invalidate];
// 记录开始的时间,hide时会用到
self.showStarted = [NSDate date];
self.alpha = 1.f;
// Needed in case we hide and re-show with the same NSProgress object attached.
[self setNSProgressDisplayLinkEnabled:YES];
if (animated) {
[self animateIn:YES withType:self.animationType completion:NULL];
} else {
// 方法弃用警告
#pragma clang diagnostic push
#pragma clang diagnostic ignored
"-Wdeprecated-declarations"
self.bezelView.alpha = self.opacity;
#pragma clang diagnostic pop
self.backgroundView.alpha = 1.f;
}
}
我们可以看到,无论是类方法的
show
方法,还是对象方法的show
方法,而且无论是触发了graceTimer
还是没有触发,最后都会走到showUsingAnimation:
方法来让HUD显示出来。
这里肯定会有人会问graceTimer
有什么用,我们来解读下,顺带把MBProgressHUD中的三个定时器属性一起介绍下:
补充:
定时器属性
// 执行一次,在show方法调用后到HUD真正显示之前执行,graceTime默认为0
@property (nonatomic, weak) NSTimer *graceTimer;
// 执行一次,在HUD显示后到HUD被隐藏之间执行
@property (nonatomic, weak) NSTimer *minShowTimer;
// 执行一次,在HUD被隐藏方法调用后到HUD真正隐藏之前执行
@property (nonatomic, weak) NSTimer *hideDelayTimer;
- graceTimer:若设置代理graceTime,那么HUD回在show方法被调用后的graceTime时间后显示。这么做的目的是为了防止如果任务完成很快,消耗的时间太短而造成HUD一闪而过的效果,用户体验很差。如果设置了graceTime,那么如果任务完成的很快,所消耗的时间少于所设置的graceTime,那么HUD则不会显示。
- minShowTimer:若设置了minShowTime,那么就会在hide方法被调用后判断任务执行时间是不是少于minShowTime,如果任务执行时间少于minShowTime,那么HUD也不会立刻被隐藏,它会等待minShowTime时间之后才消失。这么做也是为了防止HUD出现一闪而过的情况。
- hideDelayTimer:用来推迟HUD的隐藏。如果设置了delayTime,那么HUD会在hide方法触发之后等待delayTime时间才隐藏。
在showUsingAnimation:(BOOL)animated
方法里调用了[self setNSProgressDisplayLinkEnabled:YES]
方法,这个方法是用来刷新NSProgress
的
// NSProgress的监听方法
- (void)setNSProgressDisplayLinkEnabled:(BOOL)enabled {
// 通过CADisplayLink来刷新progress的变化。为啥不用KVO来监听是因为progress的变化可能很快的,如果使用KVO机制来监听progress的变化会很占用主线程。
if (enabled && self.progressObject) {
// Only create if not already active.
if (!self.progressObjectDisplayLink) {
self.progressObjectDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateProgressFromProgressObject)];
}
}
else {
// 不刷新
self.progressObjectDisplayLink = nil;
}
}
我们再来解读hide
方法:
+ (BOOL)hideHUDForView:(UIView *)view animated:(BOOL
)animated {
MBProgressHUD *hud = [self HUDForView:view]; // 获取当前view最前的HUD
if (hud != nil) {
hud.removeFromSuperViewOnHide = YES;
[hud hideAnimated:animated];
return YES;
}
return NO;
}
+ (MBProgressHUD *)HUDForView:(UIView *)view {
NSEnumerator *subviewsEnum = [view.subviews reverseObjectEnumerator]; // 倒序排序,把最早加入的子view放到最前面
for (UIView *subview in subviewsEnum) {
if ([subview isKindOfClass:self]) {
return (MBProgressHUD*)subview;
}
}
return nil;
}
- (void)hideAnimated:(BOOL)animated {
MBMainThreadAssert();
[self.graceTimer invalidate];
self.useAnimation = animated;
self.finished = YES;
// 如果设置了HUD最小的显示时间(minShowTime),则需要判断最小显示时间和已经显示的时间大小
if (self.minShowTime > 0.0 && self.showStarted) {
NSTimeInterval interv = [[NSDate date] timeIntervalSinceDate:self.showStarted];
if (interv < self.minShowTime) {
// 如果最小显示时间比较大,那么不会立刻触发HUD的隐藏,而是计算出二者的时间差,开启一个timer,经过这个时间差之后再触发隐藏
NSTimer *timer = [NSTimer timerWithTimeInterval:(self.minShowTime - interv) target:self selector:@selector(handleMinShowTimer:) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
self.minShowTimer = timer;
return;
}
}
// 如果最小显示时间比较小,则立刻触发HUD的隐藏
[self hideUsingAnimation:self.useAnimation];
}
// minShowTimer的触发方法
- (void)handleMinShowTimer:(NSTimer *)theTimer {
[self hideUsingAnimation:self.useAnimation];
}
// hide方法最后都会走到这个方法里
- (void)hideUsingAnimation:(BOOL)animated {
if (animated && self.showStarted) {
// 隐藏时,将showStared设为nil
self.showStarted = nil;
[self animateIn:NO withType:self.animationType completion:^(BOOL
finished) {
[self done];
}];
}
else {
self.showStarted = nil;
self.bezelView.alpha = 0.f;
self.backgroundView.alpha = 1.f;
[self done];
}
}
解读完
hide
方法我们可以看到,无论是类方法的hide
方法,还是对象方法的hide
方法,而且无论是触发还是没有触发minShowTimer
,最终都会走到`hideUsingAnimation这个方法里
而无论是show
方法,还是hide
方法,在设定animated
属性为YES
的前提下,最终都会走到animateIn: withType: completion:
方法:
// 无论是show方法还是hide方法,如果设置了animated属性为YES的话,最终都会走到这个方法
- (void)animateIn:(BOOL)animatingIn withType:(MBProgressHUDAnimation)type completion:(void(^)(BOOL finished))completion {
// 通过animateIn来判断动画效果
if (type == MBProgressHUDAnimationZoom) {
type = animatingIn ? MBProgressHUDAnimationZoomIn : MBProgressHUDAnimationZoomOut;
}
// 设置放大和缩小的倍数
CGAffineTransform small = CGAffineTransformMakeScale(0.5f, 0.5f);
CGAffineTransform large = CGAffineTransformMakeScale(1.5f, 1.5f);
// Set starting state
UIView *bezelView = self.bezelView;
if (animatingIn && bezelView.alpha == 0.f && type == MBProgressHUDAnimationZoomIn) {
bezelView.transform = small;
}
else if (animatingIn && bezelView.alpha == 0.f && type == MBProgressHUDAnimationZoomOut) {
bezelView.transform = large;
}
// 创建动画任务
dispatch_block_t animations = ^{
if (animatingIn) {
bezelView.transform = CGAffineTransformIdentity;
} else if (!animatingIn && type == MBProgressHUDAnimationZoomIn) {
bezelView.transform = large;
} else if (!animatingIn && type == MBProgressHUDAnimationZoomOut) {
bezelView.transform = small;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored
"-Wdeprecated-declarations"
bezelView.alpha = animatingIn ? self.opacity : 0.f;#pragma clang diagnostic pop self.backgroundView.alpha = animatingIn ? 1.f : 0.f;
};
// Spring animations are nicer, but only available on iOS 7+
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 || TARGET_OS_TV if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_0
) {
[UIView animateWithDuration:0.3 delay:0. usingSpringWithDamping:1.f initialSpringVelocity:0.f options:UIViewAnimationOptionBeginFromCurrentState animations:animations completion
:completion];
return;
}
#endif
[UIView animateWithDuration:0.3 delay:0. options:UIViewAnimationOptionBeginFromCurrentState animations:animations completion:completion];
}
总结
- 提供的外部API最终都走到同一个私有方法,通过一个参数来判断是show还是hide
- 使用CADisplayLink来刷新频率很高的view
- 使用NSAssert来捕获各种异常
写在最后
读源码MBProgressHUD就这样大致写完了,没有怎么读过第三方框架的源码,所以第一次可能漏洞百出,还希望大家多多见谅,有什么写的不对或者欠妥的地方,希望大家指出来哈~~~