写点什么

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:52858

评论

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

有哪些好用的企业即时通讯软件值得推荐?

BeeWorks

WorkPlus AI助理 | 将企业业务场景与ChatGPT结合

BeeWorks

3天速成!阿里人私用的Netty速成实战手册,3天Github星标11.5k

Java你猿哥

Java 源码 Netty ssm netty内存管理

Flutter三棵树系列之详解各种Key | 京东云技术团队

京东科技开发者

flutter key 企业号 5 月 PK 榜 localkey

Logic Pro X(苹果专业音频制作软件)v10.7.8中文版

Rose

苹果mac软件下载 Logic Pro X下载 Logic Pro X破解 Logic Pro X教程 音频制作软件

内部开发者平台|自建还是购买,企业应如何选择?

SEAL安全

平台工程 企业号 5 月 PK 榜 内部开发平台

常用的表格检测识别方法——表格结构识别方法(上)

合合技术团队

人工智能 深度学习 算法 人工智能文字识别 表格检测

2023年,Flutter3.10版本的变化有哪些?

没有用户名丶

小程序容器

SpringBoot + Docker 实现一次构建到处运行

Java你猿哥

Java Docker Spring Boot ssm 容器化部署

龙博机电:90后“厂二代”,靠伙伴云零代码让中小制造业实现数字化“逆袭”

联营汇聚

Ableton Live Suite 11破解版下载 音乐制作软件

Rose

音乐制作 Ableton Live 11中文版 Live Suite 11破解 Ableton Live Suite下载

PoseiSwap IDO在Bounce上启动在即,如何参与?

鳄鱼视界

PoseiSwap IDO在Bounce上启动在即,如何参与?

西柚子

混沌演练实践(二)-支付加挂链路演练 | 京东云技术团队

京东科技开发者

微服务 混沌工程 混沌工程实践 企业号 5 月 PK 榜

苹果Mac视频转码编辑工具Compressor v4.6.4最新中文激活版

Rose

下载 fcpx Compressor Mac下载 苹果视频编码工具 Compressor破解版

什么是 Final Cut Pro? fcpx视频剪辑下载安装

Rose

Final Cut Pro下载 Final Cut Pro破解版 FCPX软件 fcpx Mac视频剪辑软件

以敏捷性为目标,构建良好企业生态

智达方通

数据驱动 数据孤岛 智达方通 全面预算管理 数据分析系统

基于 Log 的通用增量 Checkpoint 在美团的进展

Apache Flink

大数据 flink 实时计算

CloudQuery v2.0.0 发布 新增数据保护、数据变更、连接管理等功能

BinTools图尔兹

数据库 国产数据库 版本发布

深度学习基础入门篇-序列模型:[11]:循环神经网络 RNN、长短时记忆网络LSTM、门控循环单元GRU原理和应用详解

汀丶人工智能

人工智能 深度学习 RNN LSTM GRU

深度学习进阶篇-预训练模型[1]:预训练分词Subword、ELMo、Transformer模型原理;结构;技巧以及应用详解

汀丶人工智能

人工智能 深度学习 预训练模型 Transformer ELMo

企业研发效能度量利器,华为云发布CodeArts Board看板服务

华为云开发者联盟

云计算 后端 华为云 华为云开发者联盟 企业号 5 月 PK 榜

低代码平台中的分布式RPC框架(约3000行代码)

canonical

开源 dubbo RPC框架

Mac视频后期特效工具 motion5 v5.6.4进行了额外修复和优化

Rose

mac软件下载 Motion 5 motion5中文 视频后期特效处理 Motion 5破解版

耕升 GeForce RTX 4060 Ti 系列,为玩家带来DLSS3+1080P光追游戏体验!

极客天地

Elasticsearch与Clickhouse数据存储对比 | 京东云技术团队

京东科技开发者

数据库 elasticsearch Clickhouse 企业号 5 月 PK 榜

升级正当时,高性价比的影驰 GeForce RTX™ 4060 Ti 8G开箱评测

极客天地

视频后期特效处理软件:Motion 5 最新中文激活版

真大的脸盆

Mac Mac 软件 视频特效合成 视频特效工具 特效合成

1.5万字+30张图盘点程序员面试必会MySQL索引常见的11个知识点

Java你猿哥

Java MySQL 数据 ssm 索引

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