写点什么

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

评论

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

不抖机灵!让工程师来告诉你做芯片是如何烧钱的!

IC男奋斗史

芯片 半导体 芯片测试 ATE测试

Illustrator 2024 for mac(标准矢量插画设计软件) v28.0完整激活版

mac

苹果mac Windows软件 矢量图形编辑软件 Illustrator 2024

Defi/DAPP/LP代币流动性质押挖矿系统开发(技术组件)

V\TG【ch3nguang】

文心一言 VS 讯飞星火 VS chatgpt (113)-- 算法导论10.2 5题

福大大架构师每日一题

福大大架构师每日一题

Avid Sibelius Ultimate 2023 for Mac(西贝柳斯音乐记谱软件)

Rose

西贝柳斯终极解锁版 Avid Sibelius 2023 Mac Mac乐谱制作软件

Lightroom Classic 2024更新,最新LRC2024中文激活版下载mac/win

iMac小白

Lightroom Classic2024 LrC2024

启动速度提升 10 倍:Apache Dubbo 静态化方案深入解析

阿里巴巴云原生

阿里云 云原生

ATE机台哪家强?

IC男奋斗史

芯片 半导体 职场经验 芯片测试 ATE测试

基于 Triple 实现 Web 移动端后端全面打通

阿里巴巴云原生

阿里云 微服务 云原生

CSS小技巧之单标签loader

南城FE

CSS css3 前端 Loader

Python - 字典2

小万哥

Python 程序员 软件 后端 开发

ARTS 打卡第6周

AI帅辉

ARTS 打卡计划 学习分享

可观测 AIOps 的智能监控和诊断实践丨QCon 全球软件开发大会总结

阿里巴巴云原生

阿里云 云原生 AIOPS 可观测

几款好用的苹果Mac硬盘检测工具

Rose

SSD mac软件下载 Mac硬盘健康 硬盘检测软件

Downie 4 for Mac(最好用的视频下载软件) 4.6.31中文激活版

mac

Downie4 苹果mac Windows软件 网站视频下载

Premiere Pro 2024 for mac(pr2024视频编辑器) v24.0完整激活版

mac

苹果mac Windows软件 视频剪辑软件 Premiere Pro 2024

【论文阅读】【三维场景点云分割】Superpoint Transformer for 3D Scene Instance Segmentation

AI帅辉

深度学习 论文阅读 Transformer 分割 3D点云

Bridge 2024 (BR)新功能介绍及破解安装教程

Rose

Adobe Bridge 2024 BR2024下载 Bridge 2024破解版 Bridge 2024 中文版

苹果Mac文件管理浏览软件Path Finder中文破解版 支持Mac14系统

Rose

mac文件管理软件 Path Finder Path Finder破解

Easysearch压缩模式深度比较:ZSTD+source_reuse的优势分析

极限实验室

easysearch

DeFi/DAPP质押借贷分红挖矿系统开发/详情方案

V\TG【ch3nguang】

Python开发:pycharm pro 2023 永久激活秘钥【Mac/win】

Rose

Python开发 PyCharm破解版 PyCharm Pro密钥 JetBrainsPyCharm

OpenResty 入门以及 WAF 防御实战

越长大越悲伤

nginx openresty waf

充换电企业开迈斯低成本提升线上应用稳定性的最佳实践

阿里巴巴云原生

阿里云 云原生

如何转产品工程师?

IC男奋斗史

职业规划 芯片 半导体 芯片测试 ATE测试

茶百道全链路可观测实战

阿里巴巴云原生

阿里云 云原生 可观测

Apache Dubbo 云原生可观测性的探索与实践

阿里巴巴云原生

Apache 阿里云 云原生 dubbo

蓝易云:Centos 7 通过 targz文件安装 Elastic Search服务教程!

百度搜索:蓝易云

elasticsearch Linux centos SEO targz

蓝易云:Redis相比Memcached有哪些优势?

百度搜索:蓝易云

redis memcached 云计算 Linux 云服务器

活在无限中

少油少糖八分饱

读后感 阅读 动漫 葬送的芙莉莲 有限与无限游戏

安装Linux系统对硬件的要求

芯动大师

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