MBProgressHUD source code analysis

Heard many times: “programmers want to read more good source to enhance themselves”, so similar words, but also feel that there are many of them will not, so immediately started their own reading good source code, Project.


What frame does it start with? I think of SDWebImage, but generally look down many documents, but also a lot of code, do not know where to look, so give up. So enlightened, or from the framework of the most simple start due to the learning curve to give yourself a little gentle set to increase steadily, small run is the kingly way to

Seek to find the MBProgressHUD, this framework is only two files, a header file and a file, it is suitable for my present level (for a not how to read the source code, so the player) began rolling up his sleeves.

It took about 3 hours to take notes, even though the documents were few, but there were a lot of things I didn’t know [hide face]. Overall, the harvest is still relatively large, in addition to some fragmentary grammar framework, the authors consider for design and code structure is very good, it is worth learning, and I also described below.

This summary is mainly divided into three parts to introduce this framework:

  1. Core Public API
  2. Method call flow chart
  3. Method internal implementation

Stop talking. Let’s begin


1. core Public API

1.1 attribute:

@property (assign, nonatomic) MBProgressHUDMode mode; //HUD type @property (assign, nonatomic) MBProgressHUDAnimation animationType UI_APPEARANCE_SELECTOR; / / @property animation type (assign, nonatomic) NSTimeInterval graceTime; / / show function to display the time triggered HUD @property (assign, nonatomic) NSTimeInterval minShowTime; the shortest time //HUD display

1.2 types of methods:

/ * * * in a view to add HUD and display * * Note: before the show, first remove the display in the current view HUD. This approach is rigorous, and we abstract the solution: if a model is like this: we need to add A to the B, but the requirement is that there is only one A in the B. Then each time you add A to B, you must first determine whether there is a A in the current B, and if so, remove it. * (instancetype) + showHUDAddedTo: (UIView *) view animated: (BOOL) animated; / * * * to find a view on top of HUD and hide it. * if the return value is YES, it indicates that HUD is found and removed. * (BOOL) + hideHUDForView: (UIView *) view animated: (BOOL) animated; / * * * in a view found on the top of HUD and return it. * the return value can be empty, so the return value of the keyword: nullable * + (nullable * MBProgressHUD) HUDForView: (UIView * view);

1.3 object method:

A HUD / * * * with a convenience constructor to initialize the HUD view: the view bounds is HUD bounds * / - (instancetype) initWithView: (UIView * view); / * * * HUD display, no animation. * / - (void) showAnimated: (BOOL) animated; / * * * hidden HUD, there is no animation. * / - (void) hideAnimated: (BOOL) animated; / * * * after delay HUD had no time to hide, animation. * / - (void) hideAnimated: (BOOL) animated afterDelay: (NSTimeInterval) delay;

After reading the main API, let’s look at the flow chart of the method call:

2. method call flow chart:

In general, the interface of the third party framework is neat, and can be broadly divided into two categories: show and hide. Moreover, whether you call the display method or hide the method, it will eventually go to the private method animateIn:withType: completion: (provided that the animation is added). You can see the flow chart of the method call:

MBProgressHUD source code analysis
method call flow chart

After looking at the structure of the method call, let’s look at how the method is implemented internally:

3. internal implementation of the method:

Before explaining API, it is necessary to introduce the three HUD used by Timer.

@property (nonatomic, weak) NSTimer *graceTimer; / / A: in the show method after the trigger to HUD really showed before, the premise is set to graceTime, the default is 0 @property (nonatomic, weak) NSTimer *minShowTimer; / / A: in the HUD display to be hidden before the HUD @property (nonatomic, weak) NSTimer *hideDelayTimer; / / A: trigger method is hidden in the HUD to really hide before
  • GraceTimer: used to delay the display of HUD. If graceTime is set, HUD will be displayed after the graceTime time after the show method is triggered. What it means is that if the task takes time to be very short and shorter than graceTime, then HUD does not appear, avoiding the HUD flash experience.
  • MinShowTimer: if you set minShowTime, you will determine whether the task is executed shorter than minShowTime after the hide method is triggered. Therefore, even if the task is completed before minShowTime, HUD will not disappear immediately, and it will disappear after the minShowTime, which should also prevent HUD flashing past.
  • HideDelayTimer: used to postpone HUD hiding. If you set delayTime, then HUD will not hide immediately after the hide method is triggered, and it will be hidden after the delayTime has been completed.

The relationship between the three can be represented by the following diagram (and does not include all the circumstances):

MBProgressHUD source code analysis,
, three kinds of timer

The following are the ways to explain the show series and the methods of the hide series.

3.1 show series method

(instancetype) + showHUDAddedTo: (UIView *) view animated: (BOOL) animated *hud alloc] initWithView:view] {MBProgressHUD = [[self; / / initWithFrame:view.bounds] and then call [self: set your frame hud.removeFromSuperViewOnHide = YES view according to the incoming frame; //removeFromSuperViewOnHide should be a marker that HUD himself in "should be removed" [view addSubview:hud]; / / [hud showAnimated:animated] added in the instance of view will own return; HUD;} / / call showAnimated:- (void) showAnimated: (BOOL) animated ([self.minShowTimer) {MBMainThreadAssert; invalidate]; / / cancel the current minShowTimer self.useAnimation = animated; / / set the animated state self.finished = NO; / / add tag That the current task is still in progress: / / if graceTime is set, if will show delayed HUD (self.graceTime > 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;} / /... Otherwise show the HUD immediately else {[self showUsingAnimation:self.useAnimation];}} //self.graceTimer trigger method - (void) handleGraceTimer: (NSTimer *) theTimer the HUD only {/ / Show if the task is still running if (! Self.hasFinished) {[self showUsingAnimation:self.useAnimation]}}; / / all The show method will eventually come to this method - (void) showUsingAnimation: (BOOL) animated Cancel any previous animations {/ /: move all the animation [self.bezelView.layer removeAllAnimations] [self.backgroundView.layer removeAllAnimations]; any scheduled hideDelayed:; / / Cancel calls delay timer [self.hideDelayTimer invalidate]: cancel; / / memory start time self.showStarted [NSDate = date]; self.alpha = 1.f in case we; / / Needed hide and re-show with the same NSProgress object attached. [self setNSProgressDisplayLinkEnabled:YES]; if (animated) {[self animateIn:YES withType:self.animationType} else {completion:NULL]; / / #pragma clang diagnostic push abandoned warning method #pragma, clang, diagnostic, ignored, -Wdeprecated-declarations, self.bezelView.alpha = self.opacity; #pragma, clang, diagnostic, pop, self.backgroundView.alpha = 1.f;}

We can see that both the show method method, object method or show method, and whether or not the graceTimer trigger trigger, finally will go to the showUsingAnimation: method for HUD display.

Here add to explain the NSProgress listening method:

- (void) setNSProgressDisplayLinkEnabled: (BOOL enabled) {/ / used here CADisplayLink to refresh the progress change. Because if you use the KVO mechanism to listen, you might consume the main thread very much (because the frequency can be very fast). If (enabled & & self.progressObject) {/ / Only create if not already active. if (! Self.progressObjectDisplayLink) {self.progressObjectDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector (updateProgressFromProgressObject)];}} else {/ / no refresh self.progressObjectDisplayLink = nil;}}

CADisplayLink is a can let us and the screen refresh rate synchronous frequency specific content to screen the timer class. CADisplayLink in particular patterns registered to the runloop, when displayed on the screen is refreshed, runloop will send CADisplayLink selector message to the specified target a specified CADisplayLink, the corresponding selector will be called a.
reference article: Core Animation series CADisplayLink

3.2 hide series method

(BOOL) + hideHUDForView: (UIView *) view animated: (BOOL) animated *hud HUDForView:view] {MBProgressHUD = [self; / / get the current view's HUD if (now HUD! = Nil) {hud.removeFromSuperViewOnHide = YES; [hud hideAnimated:animated]; return YES;} return NO;} + (MBProgressHUD * UIView * (HUDForView:) NSEnumerator *subviewsEnum = [view.subviews view) {reverseObjectEnumerator]; / / for (UIView *subview in flashback sequence subviewsEnum) {if ([subview isKindOfClass:self]) {return (MBProgressHUD *) subview return nil;}};} - (void) hideAnimated: (BOOL) animated ([self.graceTimer) {MBMainThreadAssert = animated; invalidate]; self.useAnimation; self.f Inished = YES; / / if you set the HUD minimum display time, it needs to determine the minimum elapsed time display time and the size of if (self.minShowTime > 0 & & self.showStarted) {NSTimeInterval interv = [[NSDate date] timeIntervalSinceDate:self.showStarted]; / / if the minimum time is relatively large, temporarily do not trigger HUD to hide, but start a timer, and then after the two time difference of time triggered if (interv < hidden self.minShowTime) {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;}} / / if the minimum time is relatively small, it will immediately hide [self HUD hideUsingAnimation:self.useAnimation];} //self.minShowTimer trigger method - (void) handleMinShowTimer: (NSTimer * theTimer) {[self hideUsingAnimation:self.useAnimation];} - (void) hideUsingAnimation: (BOOL) {if (animated & animated; & self.showStarted) {/ / hidden, set showStarted to nil self.showStarted [self animateIn:NO withType: self.animationType = nil; completion:^ (BOOL finished) {[self} else {done];}]; self.showStarted = nil; self.bezelView.alpha = 0.f; self.backgroundView.alpha = 1.f; [s Elf done];}

We can see that either the hide method of the class method or the hide method of the object method, and whether it triggers or does not trigger minShowTimer, will eventually go to the hideUsingAnimation method.

Regardless of the show method or the hide method, the animated withType: completion: method will eventually go to the animateIn: YES setting:

- (void) animateIn: (BOOL) animatingIn withType: (MBProgressHUDAnimation) type (completion: (void ^) (BOOL finished)) completion determine the correct {/ / Automatically zoom animation type if (type = = MBProgressHUDAnimationZoom) {type = animatingIn? MBProgressHUDAnimationZoomIn: MBProgressHUDAnimationZoomOut;} / / (in) on behalf of X and Y direction zoom CGAffineTransform small = CGAffineTransformMakeScale (0.5f, 0.5f); CGAffineTransform large = CGAffineTransformMakeScale (1.5f, 1.5f); / / set the initial state of UIView *bezelView if (animatingIn = self.bezelView; & & bezelView.alpha & & = 0.f; type = = MBProgressHUDAnimationZoomIn) {bezelView.transform = small;} else (animatingIn &am if P; & bezelView.alpha = 0.f & & type = = MBProgressHUDAnimationZoomOut) {bezelView.transform = large;} / / animations = ^{create animation task dispatch_block_t if (animatingIn) {bezelView.transform = CGAffineTransformIdentity;} else (if / reset! 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 animating / / if In is true, is show method, hide method or self.backgroundView.alpha = animatingIn? 1.f: 0.f;}; / / Spring animations are nicer, but only available on iOS 7+ #if __IPHONE_OS_VERSION_MAX_ALLOWED > TARGET_OS_TV if = 70000 || (kCFCoreFoundationVersionNumber = > kCFCoreFoundationVersionNumber_iOS_7_0) {/ / > = iOS7 [UIView animation implementation; 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];}

Apart from the details of the grammar, I think there are several places for us to learn from the framework:

  1. The exposed API will eventually go to the same private method, with only parameter to disk tons, the show method or the hide method.
  2. The ability to customize and stabilize the time before and after the actual display plus buffer time (graceTimer and hideDelayTimer) can be improved.
  3. If two methods are contradictory and can be invoked at the same time, you need to set a property in the global setting to determine the current state (the removeFromSuperViewOnHide attribute, the finished property)
  4. Use CADisplayLink to refresh the view that may be very high in update frequency.
  5. Use NSAssert to capture various exceptions.

In this way, roughly finished, and did not read the source code of the third party framework, so the first time may seem a little inadequate. There are bad places, but also hope that a lot of pointing Kazakhstan ~!

Oh, yes, and one more thing, the author’s personal homepage is officially open. Most of the articles in Jane’s book will be copied into it. If the blog is released later, the two will be released at the same time

Because some time ago to learn H5 and CSS3, so I think the blog theme is not good place to spend time on their own tune, and the overall effect is more satisfactory:

MBProgressHUD source code analysis
blog screenshot

This paper has been synchronized to my personal technology blog: portal, welcome to.


This article has been filed on copyright record. If you want to reprint, please visit copyright print. Forty-eight million four hundred and twenty-two thousand nine hundred and twenty-eight

Obtain authorization