写点什么

iOS 源代码分析 ---- MBProgressHUD

  • 2019-12-10
  • 本文字数:5278 字

    阅读完需:约 17 分钟

iOS 源代码分析 ---- MBProgressHUD

MBProgressHUD 是一个为 iOS app 添加透明浮层 HUD 的第三方框架. 作为一个 UI 层面的框架, 它的实现很简单, 但是其中也有一些非常有意思的代码.

MBProgressHUD

MBProgressHUD 是一个 UIView 的子类, 它提供了一系列的创建 HUD 的方法. 我们在这里会主要介绍三种使用 HUD 的方法.


  • + showHUDAddedTo:animated:

  • - showAnimated:whileExecutingBlock:onQueue:completionBlock:

  • - showWhileExecuting:onTarget:withObject:

+ showHUDAddedTo:animated:

MBProgressHUD 提供了一对类方法 + showHUDAddedTo:animated:+ hideHUDForView:animated: 来创建和隐藏 HUD, 这是创建和隐藏 HUD 最简单的一组方法


Objective-C


+ (MB_INSTANCETYPE)showHUDAddedTo:(UIView *)view animated:(BOOL)animated {  MBProgressHUD *hud = [[self alloc] initWithView:view];  hud.removeFromSuperViewOnHide = YES;  [view addSubview:hud];  [hud show:animated];  return MB_AUTORELEASE(hud);}
复制代码

- initWithView:

首先调用 + alloc - initWithView: 方法返回一个 MBProgressHUD 的实例, - initWithView: 方法会调用当前类的 - initWithFrame: 方法.


通过 - initWithFrame: 方法的执行, 会为 MBProgressHUD 的一些属性设置一系列的默认值.


Objective-C


- (id)initWithFrame:(CGRect)frame {  self = [super initWithFrame:frame];  if (self) {    // Set default values for properties    self.animationType = MBProgressHUDAnimationFade;    self.mode = MBProgressHUDModeIndeterminate;    ...    // Make it invisible for now    self.alpha = 0.0f;
[self registerForKVO]; ... } return self;}
复制代码


MBProgressHUD 初始化的过程中, 有一个需要注意的方法 - registerForKVO, 我们会在之后查看该方法的实现.

- show:

在初始化一个 HUD 并添加到 view 上之后, 这时 HUD 并没有显示出来, 因为在初始化时, view.alpha 被设置为 0. 所以我们接下来会调用 - show: 方法使 HUD 显示到屏幕上.


Objective-C


- (void)show:(BOOL)animated {    NSAssert([NSThread isMainThread], @"MBProgressHUD needs to be accessed on the main thread.");  useAnimation = animated;  // If the grace time is set postpone the HUD display  if (self.graceTime > 0.0) {        NSTimer *newGraceTimer = [NSTimer timerWithTimeInterval:self.graceTime target:self selector:@selector(handleGraceTimer:) userInfo:nil repeats:NO];        [[NSRunLoop currentRunLoop] addTimer:newGraceTimer forMode:NSRunLoopCommonModes];        self.graceTimer = newGraceTimer;  }  // ... otherwise show the HUD imediately  else {    [self showUsingAnimation:useAnimation];  }}
复制代码


因为在 iOS 开发中, 对于 UIView 的处理必须在主线程中, 所以在这里我们要先用 [NSThread isMainThread] 来确认当前前程为主线程.


如果 graceTime0, 那么直接调用 - showUsingAnimation: 方法, 否则会创建一个 newGraceTimer 当然这个 timer 对应的 selector 最终调用的也是 - showUsingAnimation: 方法.

- showUsingAnimation:

Objective-C


- (void)showUsingAnimation:(BOOL)animated {    // Cancel any scheduled hideDelayed: calls    [NSObject cancelPreviousPerformRequestsWithTarget:self];    [self setNeedsDisplay];
if (animated && animationType == MBProgressHUDAnimationZoomIn) { self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(0.5f, 0.5f)); } else if (animated && animationType == MBProgressHUDAnimationZoomOut) { self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(1.5f, 1.5f)); } self.showStarted = [NSDate date]; // Fade in if (animated) { [UIView beginAnimations:nil context:NULL]; [UIView setAnimationDuration:0.30]; self.alpha = 1.0f; if (animationType == MBProgressHUDAnimationZoomIn || animationType == MBProgressHUDAnimationZoomOut) { self.transform = rotationTransform; } [UIView commitAnimations]; } else { self.alpha = 1.0f; }}
复制代码


这个方法的核心功能就是根据 animationTypeHUD 的出现添加合适的动画.


Objective-C


typedef NS_ENUM(NSInteger, MBProgressHUDAnimation) {  /** Opacity animation */  MBProgressHUDAnimationFade,  /** Opacity + scale animation */  MBProgressHUDAnimationZoom,  MBProgressHUDAnimationZoomOut = MBProgressHUDAnimationZoom,  MBProgressHUDAnimationZoomIn};
复制代码


它在方法刚调用时会通过 - cancelPreviousPerformRequestsWithTarget: 移除附加在 HUD 上的所有 selector, 这样可以保证该方法不会多次调用.


同时也会保存 HUD 的出现时间.


Objective-C


self.showStarted = [NSDate date]
复制代码

+ hideHUDForView:animated:

Objective-C


+ (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated {  MBProgressHUD *hud = [self HUDForView:view];  if (hud != nil) {    hud.removeFromSuperViewOnHide = YES;    [hud hide:animated];    return YES;  }  return NO;}
复制代码


+ hideHUDForView:animated: 方法的实现和 + showHUDAddedTo:animated: 差不多, + HUDForView: 方法会返回对应 view 最上层的 MBProgressHUD 的实例.


Objective-C


+ (MB_INSTANCETYPE)HUDForView:(UIView *)view {  NSEnumerator *subviewsEnum = [view.subviews reverseObjectEnumerator];  for (UIView *subview in subviewsEnum) {    if ([subview isKindOfClass:self]) {      return (MBProgressHUD *)subview;    }  }  return nil;}
复制代码


然后调用的 - hide: 方法和 - hideUsingAnimation: 方法也没有什么特别的, 只有在 HUD 隐藏之后 - done 负责隐藏执行 completionBlockdelegate 回调.


Objective-C


- (void)done {  [NSObject cancelPreviousPerformRequestsWithTarget:self];  isFinished = YES;  self.alpha = 0.0f;  if (removeFromSuperViewOnHide) {    [self removeFromSuperview];  }#if NS_BLOCKS_AVAILABLE  if (self.completionBlock) {    self.completionBlock();    self.completionBlock = NULL;  }#endif  if ([delegate respondsToSelector:@selector(hudWasHidden:)]) {    [delegate performSelector:@selector(hudWasHidden:) withObject:self];  }}
复制代码

- showAnimated:whileExecutingBlock:onQueue:completionBlock:

block 指定的队列执行时, 显示 HUD, 并在 HUD 消失时, 调用 completion.


同时 MBProgressHUD 也提供一些其他的便利方法实现这一功能:


Objective-C


- (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block;- (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block completionBlock:(MBProgressHUDCompletionBlock)completion;- (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue;
复制代码


该方法会异步在指定 queue 上运行 block 并在 block 执行结束调用 - cleanUp.


- (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue   completionBlock:(MBProgressHUDCompletionBlock)completion {  self.taskInProgress = YES;  self.completionBlock = completion;  dispatch_async(queue, ^(void) {    block();    dispatch_async(dispatch_get_main_queue(), ^(void) {      [self cleanUp];    });  });  [self show:animated];}
复制代码


关于 - cleanUp 我们会在下一段中介绍.

- showWhileExecuting:onTarget:withObject:

当一个后台任务在新线程中执行时, 显示 HUD.


Objective-C


- (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated {  methodForExecution = method;  targetForExecution = MB_RETAIN(target);  objectForExecution = MB_RETAIN(object);  // Launch execution in new thread  self.taskInProgress = YES;  [NSThread detachNewThreadSelector:@selector(launchExecution) toTarget:self withObject:nil];  // Show HUD view  [self show:animated];}
复制代码


在保存 methodForExecution targetForExecutionobjectForExecution 之后, 会在新的线程中调用方法.


Objective-C


- (void)launchExecution {  @autoreleasepool {#pragma clang diagnostic push#pragma clang diagnostic ignored "-Warc-performSelector-leaks"    // Start executing the requested task    [targetForExecution performSelector:methodForExecution withObject:objectForExecution];#pragma clang diagnostic pop    // Task completed, update view in main thread (note: view operations should    // be done only in the main thread)    [self performSelectorOnMainThread:@selector(cleanUp) withObject:nil waitUntilDone:NO];  }}
复制代码


- launchExecution 会创建一个自动释放池, 然后再这个自动释放池中调用方法, 并在方法调用结束之后在主线程执行 - cleanUp.

Trick

MBProgressHUD 中有很多神奇的魔法来解决一些常见的问题.

ARC

MBProgressHUD 使用了一系列神奇的宏定义来兼容 MRC.


Objective-C


#ifndef MB_INSTANCETYPE#if __has_feature(objc_instancetype)  #define MB_INSTANCETYPE instancetype#else  #define MB_INSTANCETYPE id#endif#endif
#ifndef MB_STRONG#if __has_feature(objc_arc) #define MB_STRONG strong#else #define MB_STRONG retain#endif#endif
#ifndef MB_WEAK#if __has_feature(objc_arc_weak) #define MB_WEAK weak#elif __has_feature(objc_arc) #define MB_WEAK unsafe_unretained#else #define MB_WEAK assign#endif#endif
复制代码


通过宏定义 __has_feature 来判断当前环境是否启用了 ARC, 使得不同环境下宏不会出错.

KVO

MBProgressHUD 通过 @property 生成了一系列的属性.


Objective-C


- (NSArray *)observableKeypaths {  return [NSArray arrayWithObjects:@"mode", @"customView", @"labelText", @"labelFont", @"labelColor",      @"detailsLabelText", @"detailsLabelFont", @"detailsLabelColor", @"progress", @"activityIndicatorColor", nil];}
复制代码


这些属性在改变的时候不会, 重新渲染整个 view, 我们在一般情况下覆写 setter 方法, 然后再 setter 方法中刷新对应的属性, 在 MBProgressHUD 中使用 KVO 来解决这个问题.


Objective-C


- (void)registerForKVO {  for (NSString *keyPath in [self observableKeypaths]) {    [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL];  }}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (![NSThread isMainThread]) { [self performSelectorOnMainThread:@selector(updateUIForKeypath:) withObject:keyPath waitUntilDone:NO]; } else { [self updateUIForKeypath:keyPath]; }}
- (void)updateUIForKeypath:(NSString *)keyPath { if ([keyPath isEqualToString:@"mode"] || [keyPath isEqualToString:@"customView"] || [keyPath isEqualToString:@"activityIndicatorColor"]) { [self updateIndicators]; } else if ([keyPath isEqualToString:@"labelText"]) { label.text = self.labelText; } else if ([keyPath isEqualToString:@"labelFont"]) { label.font = self.labelFont; } else if ([keyPath isEqualToString:@"labelColor"]) { label.textColor = self.labelColor; } else if ([keyPath isEqualToString:@"detailsLabelText"]) { detailsLabel.text = self.detailsLabelText; } else if ([keyPath isEqualToString:@"detailsLabelFont"]) { detailsLabel.font = self.detailsLabelFont; } else if ([keyPath isEqualToString:@"detailsLabelColor"]) { detailsLabel.textColor = self.detailsLabelColor; } else if ([keyPath isEqualToString:@"progress"]) { if ([indicator respondsToSelector:@selector(setProgress:)]) { [(id)indicator setValue:@(progress) forKey:@"progress"]; } return; } [self setNeedsLayout]; [self setNeedsDisplay];}
复制代码


- observeValueForKeyPath:ofObject:change:context: 方法中的代码是为了保证 UI 的更新一定是在主线程中, 而 - updateUIForKeypath: 方法负责 UI 的更新.

End

MBProgressHUD 由于是一个 UI 的第三方库, 所以它的实现还是挺简单的.


本文转载自 Draveness 技术博客。


原文链接:https://draveness.me/ios-yuan-dai-ma-fen-xi-mbprogresshud


2019-12-10 17:52841

评论

发布
暂无评论
发现更多内容

Deno会在短期内取代Node吗?

葡萄城技术团队

node.js SpreadJS deno

H2 的全文检索功能

Page

全文检索 lucene H2 内存数据库

《从0到1学习Flink》—— Flink parallelism 和 Slot 介绍

zhisheng

大数据 flink 流计算

那个业务大拿死在了这个地方

小眼睛聊技术

Java 学习 高效工作 程序员 个人成长

露营之美,在乎山水之间也

李冬梅

k8s上运行我们的springboot服务之——k8s 1.16.0安装

柠檬

k8s

奈学教育分享:Hadoop分布式系统HDFS工作原理

奈学教育

hadoop hdfs 分布式

Flink 从0到1学习—— Flink 不可以连续 Split(分流)?

zhisheng

大数据 flink 流计算

Neo4j执行计划

脚动两轮男之漂流小王子

游戏夜读 | 数据整理的难题?

game1night

Jenkins 插件开发之旅:两天内从 idea 到发布(上篇)

donghui

DevOps jenkins jenkins-plugin

一文搞懂RSA算法

somenzz

k8s上运行我们的springboot服务之——在linux安装docker并搭建docker私服

柠檬

Docker k8s

《从0到1学习Flink》—— Flink JobManager 高可用性配置

zhisheng

大数据 flink 流计算

《从0到1学习Flink》—— 你上传的 jar 包藏到哪里去了?

zhisheng

大数据 flink 流计算

重学 Java 设计模式:实战工厂方法模式

小傅哥

设计模式 小傅哥 重构 架构设计 工厂模式

如何参与开源项目

郭旭东

GitHub 开源

北大学子手写实现《统计学习方法》书中全部算法!

GitHubDaily

人工智能 GitHub 学习 程序员

1分钱秒杀!疫情季,如何为孩子的升学保驾护航?

极客编

JVM源码分析之堆内存的初始化

猿灯塔

Flink 从0到1学习—— 分享四本 Flink 国外的书和二十多篇 Paper 论文

zhisheng

大数据 flink 流计算

职场提问的“唐太宗”原则

大伟

如果你想做汽车开发,请先看看这篇。

水滴

自动驾驶 软件开发 开发

聊一聊采访外籍人员时需要注意的几点事项

李冬梅

态度 体验 感悟

你不知道的JSON.stringify(上)

前端黑板报

Java json

2020年4月云主机性能评测报告

博睿数据

云计算 百度云 ucloud 性能测试 公有云

Jenkins 插件开发之旅:两天内从 idea 到发布(下篇)

donghui

DevOps jenkins jenkins-plugin

DDD 实践手册(番外篇: 事件风暴-实践)

Joshua

领域驱动设计 DDD 事件风暴 事件驱动 Event Storming

k8s上运行我们的springboot服务之——上传服务到docker私服

柠檬

Docker springboot

Flink 从0到1学习 —— 如何使用 Side Output 来分流?

zhisheng

大数据 flink 流计算

招联金融助力经济复苏 致力成为“智慧生活的消费金融专家”

极客编

iOS 源代码分析 ---- MBProgressHUD_语言 & 开发_Draveness_InfoQ精选文章