這段時(shí)間垃你,把公司新版本做完后椅文,開(kāi)始加強(qiáng)自己的學(xué)習(xí),自己看一些比較好的第三方庫(kù)源碼惜颇,從簡(jiǎn)單做起皆刺,一口一口吃好飯。
先從UI方面的第三方控件看起凌摄,最有名的就是MBProgressHUD
閱讀優(yōu)秀的第三方源碼的優(yōu)勢(shì)羡蛾,就是可以看到別人的代碼規(guī)范,從而更好的了解自己和大神之間的距離锨亏。
代碼規(guī)范
首先是外部變量的書(shū)寫(xiě)
//MBProgressHUD.h
extern CGFloat const MBProgressMaxOffset;
//MBProgressHUD.m
CGFloat const MBProgressMaxOffset = 1000000.f;
接下來(lái)是block的書(shū)寫(xiě)
typedef void (^MBProgressHUDCompletionBlock)();
/**
* Displays a simple HUD window containing a progress indicator and two optional labels for short messages.
*
* This is a simple drop-in class for displaying a progress HUD view similar to Apple's private UIProgressHUD class.
* The MBProgressHUD window spans over the entire space given to it by the initWithFrame: constructor and catches all
* user input on this region, thereby preventing the user operations on components below the view.
*
* @note To still allow touches to pass through the HUD, you can set hud.userInteractionEnabled = NO.
* @attention MBProgressHUD is a UI class and should therefore only be accessed on the main thread.
*/
從函數(shù)頭部的注釋來(lái)閱讀可以得出
這里說(shuō)明了MBProgressHUD是UI控件痴怨,所以必須在主線(xiàn)程執(zhí)行
寫(xiě)注釋的時(shí)候,先@note(備注)器予,再@param(參數(shù))浪藻,然后是@return(返回值),最后是@see(提示函數(shù))
/**
* Creates a new HUD, adds it to provided view and shows it. The counterpart to this method is hideHUDForView:animated:.
*
* @note This method sets removeFromSuperViewOnHide. The HUD will automatically be removed from the view hierarchy when hidden.
*
* @param view The view that the HUD will be added to
* @param animated If set to YES the HUD will appear using the current animationType. If set to NO the HUD will not use
* animations while appearing.
* @return A reference to the created HUD.
*
* @see hideHUDForView:animated:
* @see animationType
*/
+ (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated;
/**
* Finds the top-most HUD subview and returns it.
*
* @param view The view that is going to be searched.
* @return A reference to the last HUD subview discovered.
*/
+ (nullable MBProgressHUD *)HUDForView:(UIView *)view;
如果是用instanceType就不用用nullable
但是如果是使用MBProgressHUD就可以用nullable
解析代碼
整個(gè)文件中 公開(kāi)的方法有
/**
* 在某個(gè)view上添加HUD并顯示
*
* 注意:顯示之前乾翔,先去掉在當(dāng)前view上顯示的HUD爱葵。這個(gè)做法很?chē)?yán)謹(jǐn),我們將這個(gè)方案抽象出來(lái):如果一個(gè)模型是這樣的:我們需要將A加入到B中,但是需求上B里面只允許只有一個(gè)A钧惧。那么每次將A添加到B之前暇韧,都要先判斷當(dāng)前的b里面是否有A,如果有浓瞪,則移除。
*/
+ (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated;
/**
* 找到某個(gè)view上最上層的HUD并隱藏它巧婶。
* 如果返回值是YES的話(huà)乾颁,就表明HUD被找到而且被移除了。
*/
+ (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated;
/**
* 在某個(gè)view上找到最上層的HUD并返回它艺栈。
* 返回值可以是空英岭,所以返回值的關(guān)鍵字為:nullable
*/
+ (nullable MBProgressHUD *)HUDForView:(UIView *)view;
/**
* 一個(gè)HUD的便利構(gòu)造函數(shù),用某個(gè)view來(lái)初始化HUD:這個(gè)view的bounds就是HUD的bounds
*/
- (instancetype)initWithView:(UIView *)view;
/**
* 顯示HUD湿右,有無(wú)動(dòng)畫(huà)诅妹。
*/
- (void)showAnimated:(BOOL)animated;
/**
* 隱藏HUD,有無(wú)動(dòng)畫(huà)毅人。
*/
- (void)hideAnimated:(BOOL)animated;
/**
* 隱藏HUD吭狡,有無(wú)動(dòng)畫(huà),且可以設(shè)置延時(shí)
*/
- (void)hideAnimated:(BOOL)animated afterDelay:(NSTimeInterval)delay;
比較重要的部分:
三個(gè)NSSTimer
@property (nonatomic, weak) NSTimer *graceTimer;
@property (nonatomic, weak) NSTimer *minShowTimer;
@property (nonatomic, weak) NSTimer *hideDelayTimer;
和一個(gè)CADisplayLink
@property (nonatomic, weak) CADisplayLink *progressObjectDisplayLink;
代碼執(zhí)行
剛剛在上文提及丈莺,整個(gè)文件中划煮,比較復(fù)雜的就是上面三個(gè)timer的關(guān)系
@property (nonatomic, weak) NSTimer *graceTimer; //執(zhí)行一次:在show方法觸發(fā)后到HUD真正顯示之前,前提是設(shè)定了graceTime,默認(rèn)為0
@property (nonatomic, weak) NSTimer *minShowTimer;//執(zhí)行一次:在HUD顯示后到HUD被隱藏之前
@property (nonatomic, weak) NSTimer *hideDelayTimer;//執(zhí)行一次:在HUD被隱藏的方法觸發(fā)后到真正隱藏之前
graceTimer / minShowTimer (NSTimer)
graceTimer和minShowTimer的使用場(chǎng)所
- (void)showAnimated:(BOOL)animated {
MBMainThreadAssert();
[self.minShowTimer invalidate];
self.useAnimation = animated;
self.finished = NO;
// If the grace time is set, postpone the HUD display
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;
}
// ... otherwise show the HUD immediately
else {
[self showUsingAnimation:self.useAnimation];
}
}
- (void)hideAnimated:(BOOL)animated {
MBMainThreadAssert();
[self.graceTimer invalidate];
self.useAnimation = animated;
self.finished = YES;
// If the minShow time is set, calculate how long the HUD was shown,
// and postpone the hiding operation if necessary
if (self.minShowTime > 0.0 && self.showStarted) {
NSTimeInterval interv = [[NSDate date] timeIntervalSinceDate:self.showStarted];
if (interv < 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;
}
}
// ... otherwise hide the HUD immediately
[self hideUsingAnimation:self.useAnimation];
}
- (void)handleGraceTimer:(NSTimer *)theTimer {
// Show the HUD only if the task is still running
if (!self.hasFinished) {
[self showUsingAnimation:self.useAnimation];
}
}
- (void)handleMinShowTimer:(NSTimer *)theTimer {
[self hideUsingAnimation:self.useAnimation];
}
從上面可以看出graceTimer他的實(shí)際含義就是
如果開(kāi)發(fā)者設(shè)置了graceTime缔俄,那么MBProgressHUD不會(huì)立即顯現(xiàn)弛秋,而是在graceTime之后才調(diào)用showUsingAnimation來(lái)展示出MBProgressHUD
從上面可以看出minShowTimer他的實(shí)際含義就是
如果開(kāi)發(fā)者設(shè)置了minShowTimer,那么MBProgressHUD會(huì)在調(diào)用hide方法之前俐载,判斷當(dāng)前的時(shí)間蟹略,如果當(dāng)前的時(shí)間小于minShowTime,那么就會(huì)取消hide操作遏佣,開(kāi)啟延時(shí)挖炬,直到展示的時(shí)間達(dá)到minShowTime的時(shí)候,才調(diào)用hide操作來(lái)隱藏MBProgressHUD
hideDelayTimer(NSTimer)
hideDelayTimer 的使用場(chǎng)所
- (void)showUsingAnimation:(BOOL)animated {
// Cancel any previous animations
[self.bezelView.layer removeAllAnimations];
[self.backgroundView.layer removeAllAnimations];
// Cancel any scheduled hideDelayed: calls
[self.hideDelayTimer invalidate];
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;
}
}
- (void)done {
// Cancel any scheduled hideDelayed: calls
[self.hideDelayTimer invalidate];
[self setNSProgressDisplayLinkEnabled:NO];
if (self.hasFinished) {
self.alpha = 0.0f;
if (self.removeFromSuperViewOnHide) {
[self removeFromSuperview];
}
}
MBProgressHUDCompletionBlock completionBlock = self.completionBlock;
if (completionBlock) {
completionBlock();
}
id<MBProgressHUDDelegate> delegate = self.delegate;
if ([delegate respondsToSelector:@selector(hudWasHidden:)]) {
[delegate performSelector:@selector(hudWasHidden:) withObject:self];
}
}
//done函數(shù)方法的調(diào)用
- (void)hideUsingAnimation:(BOOL)animated {
if (animated && self.showStarted) {
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];
}
}
- (void)hideAnimated:(BOOL)animated afterDelay:(NSTimeInterval)delay {
NSTimer *timer = [NSTimer timerWithTimeInterval:delay target:self selector:@selector(handleHideTimer:) userInfo:@(animated) repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
self.hideDelayTimer = timer;
}
- (void)handleHideTimer:(NSTimer *)timer {
[self hideAnimated:[timer.userInfo boolValue]];
}
從上面可以看出來(lái)贼急,delayHideTimer的處理很聰明茅茂,其實(shí)整個(gè)文件中,核心調(diào)用隱藏的只有hideAnimated:方法太抓,所以hideAnimated:(BOOL)animated afterDelay的實(shí)現(xiàn)策略空闲,就是通過(guò)設(shè)置一個(gè)定時(shí)器,在afterDelay后再調(diào)用hideAnimated:方法走敌,從而實(shí)現(xiàn)在設(shè)置延時(shí)之后再隱藏的視覺(jué)效果碴倾,good!
progressObjectDisplayLink(CADisplayLink)
我們先從官方文檔了解一下CADisplayLink
A CADisplay?Link object is a timer object that allows your application to synchronize its drawing to the refresh rate of the display.
//CADisplayLink是一個(gè)定時(shí)器,獲取應(yīng)用中繪制屏幕刷新的速率來(lái)同步執(zhí)行操作
Your application initializes a new display link, providing a target object and a selector to be called when the screen is updated. To synchronize your display loop with the display, your application adds it to a run loop using the add?To?Run?Loop:?for?Mode:? method
//整個(gè)應(yīng)用會(huì)初始化一個(gè)定時(shí)器跌榔,提供一個(gè)target目標(biāo)和一個(gè)回調(diào)的selector异雁,當(dāng)屏幕更新的時(shí)候會(huì)回調(diào)該方法,我們應(yīng)該將定時(shí)器添加入runLoop當(dāng)中僧须,使用add?To?Run?Loop:?for?Mode:
Once the display link is associated with a run loop, the selector on the target is called when the screen’s contents need to be updated. The target can read the display link’s timestamp
property to retrieve the time that the previous frame was displayed. For example, an application that displays movies might use the timestamp to calculate which video frame will be displayed next. An application that performs its own animations might use the timestamp to determine where and how displayed objects appear in the upcoming frame. The duration
property provides the amount of time between frames. You can use this value in your application to calculate the frame rate of the display, the approximate time that the next frame will be displayed, and to adjust the drawing behavior so that the next frame is prepared in time to be displayed.
//當(dāng)displayLink和runloop連接的時(shí)候纲刀,回調(diào)方法會(huì)在屏幕更新的時(shí)候調(diào)用,同時(shí)target會(huì)獲得對(duì)應(yīng)的時(shí)間搓的屬性來(lái)獲得上一個(gè)frame展示的時(shí)候的時(shí)間搓担平,例如示绊,當(dāng)一個(gè)應(yīng)用在播放電影的時(shí)候,可以使用這個(gè)時(shí)間搓來(lái)計(jì)算出接下來(lái)要展示哪一個(gè)電影片段暂论,一個(gè)包含動(dòng)畫(huà)的應(yīng)用中可以通過(guò)使用這個(gè)時(shí)間搓來(lái)確定應(yīng)該在哪里面褐,以什么方式來(lái)展示下一個(gè)動(dòng)畫(huà)片段,duration這個(gè)屬性則是提供不同frame改變之間的所間隔的時(shí)間取胎,你可以使用這個(gè)值幫助你計(jì)算出你的應(yīng)用中展哭,frame變化的比率,以及下一個(gè)frame出現(xiàn)的大致時(shí)間闻蛀,根據(jù)這些繪制的行為匪傍,會(huì)讓你的下一個(gè)frame在準(zhǔn)確的時(shí)間內(nèi)展示出來(lái)
Your application can disable notifications by setting the paused
property to YES
. Also, if your application cannot provide frames in the time provided, you may want to choose a slower frame rate. An application with a slower but consistent frame rate appears smoother to the user than an application that skips frames. You can define the number of frames per second by setting the preferred?Frames?Per?Second
property.
//你的應(yīng)用可以停止消息提醒,當(dāng)你設(shè)置pause屬性為yes循榆,此外析恢,如果你的應(yīng)用程序無(wú)法提供所提供的時(shí)間內(nèi)的幀,您可能需要選擇較慢的幀速率秧饮。使用較慢但一致的幀速率的應(yīng)用程序比跳過(guò)幀的應(yīng)用程序更適合用戶(hù)使用映挂。您可以通過(guò)設(shè)置preferred?Frames?Per?Second屬性來(lái)定義每秒幀數(shù)。
When your application finishes with a display link, it should call invalidate
to remove it from all run loops and to disassociate it from the target.
//當(dāng)你的應(yīng)用使用完display link盗尸,請(qǐng)使用invalidate去移除runloop 并且將其從對(duì)象中取消關(guān)聯(lián)
CADisplay?Link should not be subclassed.
CADisplayLink的使用場(chǎng)所有
- (void)setProgressObjectDisplayLink:(CADisplayLink *)progressObjectDisplayLink {
if (progressObjectDisplayLink != _progressObjectDisplayLink) {
[_progressObjectDisplayLink invalidate];
_progressObjectDisplayLink = progressObjectDisplayLink;
[_progressObjectDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
}
- (void)setNSProgressDisplayLinkEnabled:(BOOL)enabled {
// We're using CADisplayLink, because NSProgress can change very quickly and observing it may starve the main thread,
// so we're refreshing the progress only every frame draw
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;
}
}
- (void)updateProgressFromProgressObject {
self.progress = self.progressObject.fractionCompleted;
}
調(diào)用setNSProgressDisplayLinkEnabled的場(chǎng)所
- (void)showUsingAnimation:(BOOL)animated {
// Cancel any previous animations
[self.bezelView.layer removeAllAnimations];
[self.backgroundView.layer removeAllAnimations];
// Cancel any scheduled hideDelayed: calls
[self.hideDelayTimer invalidate];
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;
}
}
- (void)done {
// Cancel any scheduled hideDelayed: calls
[self.hideDelayTimer invalidate];
[self setNSProgressDisplayLinkEnabled:NO];
if (self.hasFinished) {
self.alpha = 0.0f;
if (self.removeFromSuperViewOnHide) {
[self removeFromSuperview];
}
}
MBProgressHUDCompletionBlock completionBlock = self.completionBlock;
if (completionBlock) {
completionBlock();
}
id<MBProgressHUDDelegate> delegate = self.delegate;
if ([delegate respondsToSelector:@selector(hudWasHidden:)]) {
[delegate performSelector:@selector(hudWasHidden:) withObject:self];
}
}
- (void)setProgressObject:(NSProgress *)progressObject {
if (progressObject != _progressObject) {
_progressObject = progressObject;
[self setNSProgressDisplayLinkEnabled:YES];
}
}
/**
* The NSProgress object feeding the progress information to the progress indicator.
*/
@property (strong, nonatomic, nullable) NSProgress *progressObject;
/**
* The progress of the progress indicator, from 0.0 to 1.0. Defaults to 0.0.
*/
@property (assign, nonatomic) float progress;
所以從上面可以看出來(lái)柑船,當(dāng)開(kāi)發(fā)者設(shè)置progressObject的時(shí)候,就自動(dòng)會(huì)在動(dòng)畫(huà)展示的時(shí)候調(diào)用 [self setNSProgressDisplayLinkEnabled:YES]; 從而啟動(dòng)定時(shí)器泼各,因此會(huì)在每次屏幕更新的時(shí)候鞍时,調(diào)用一次updateProgressFromProgressObject來(lái)獲取當(dāng)前的fractionCompleted,從而更新progress扣蜻,在外面可以使用kvo來(lái)監(jiān)聽(tīng)progress來(lái)做一些用戶(hù)自己想要做的操作逆巍。
NSTimer和CADisplayLink
下面結(jié)合NSTimer來(lái)介紹 CADisplayLink,與NSTimer不同的地方有:
1莽使、原理不同
CADisplayLink是一個(gè)能讓我們以和屏幕刷新率同步的頻率將特定的內(nèi)容畫(huà)到屏幕上的定時(shí)器類(lèi)锐极。 CADisplayLink以特定模式注冊(cè)到runloop后, 每當(dāng)屏幕顯示內(nèi)容刷新結(jié)束的時(shí)候芳肌,runloop就會(huì)向 CADisplayLink指定的target發(fā)送一次指定的selector消息灵再, CADisplayLink類(lèi)對(duì)應(yīng)的selector就會(huì)被調(diào)用一次肋层。
NSTimer以指定的模式注冊(cè)到runloop后,每當(dāng)設(shè)定的周期時(shí)間到達(dá)后翎迁,runloop會(huì)向指定的target發(fā)送一次指定的selector消息栋猖。
2、周期設(shè)置方式不同
iOS設(shè)備的屏幕刷新頻率(FPS)是60Hz汪榔,因此CADisplayLink的selector 默認(rèn)調(diào)用周期是每秒60次蒲拉,這個(gè)周期可以通過(guò)frameInterval屬性設(shè)置, CADisplayLink的selector每秒調(diào)用次數(shù)=60/ frameInterval揍异。比如當(dāng) frameInterval設(shè)為2全陨,每秒調(diào)用就變成30次。因此衷掷, CADisplayLink 周期的設(shè)置方式略顯不便。
NSTimer的selector調(diào)用周期可以在初始化時(shí)直接設(shè)定柿菩,相對(duì)就靈活的多戚嗅。
3、精確度不同
iOS設(shè)備的屏幕刷新頻率是固定的枢舶,CADisplayLink在正常情況下會(huì)在每次刷新結(jié)束都被調(diào)用懦胞,精確度相當(dāng)高。
NSTimer的精確度就顯得低了點(diǎn)凉泄,比如NSTimer的觸發(fā)時(shí)間到的時(shí)候躏尉,runloop如果在忙于別的調(diào)用,觸發(fā)時(shí)間就會(huì)推遲到下一個(gè)runloop周期后众。更有甚者胀糜,在OS X v10.9以后為了盡量避免在NSTimer觸發(fā)時(shí)間到了而去中斷當(dāng)前處理的任務(wù),NSTimer新增了tolerance屬性蒂誉,讓用戶(hù)可以設(shè)置可以容忍的觸發(fā)的時(shí)間范圍教藻。
4、使用場(chǎng)合
從原理上不難看出右锨, CADisplayLink 使用場(chǎng)合相對(duì)專(zhuān)一括堤, 適合做界面的不停重繪,比如視頻播放的時(shí)候需要不停地獲取下一幀用于界面渲染绍移。
NSTimer的使用范圍要廣泛的多悄窃,各種需要單次或者循環(huán)定時(shí)處理的任務(wù)都可以使用。
可以去閱讀這篇文章重點(diǎn)講CADisplayLink
總結(jié)
總的而言蹂窖,MBProgressHUD總體難度不大轧抗,但是學(xué)到很多東西:
1.代碼規(guī)范
2.思路很聰明,所有暴露的方法恼策,最終都是調(diào)用同一個(gè)私有方法鸦致,在處理是否延時(shí)的時(shí)候依然使用這個(gè)思路潮剪,同樣的方法執(zhí)行,加一個(gè)定時(shí)器來(lái)區(qū)分罷了分唾。
3.使用CADisplayLink
4.使用NSAssert