進度: NSProgress

在看一些大神寫的開源框架時, 遇到了一個用于記錄進度的類 - NSProgress, 然后通過看官方文檔和網上找資料的方式, 來學習這個類的用法, 并在此記錄此類的用法, 如果有什么地方錯誤請留言, 我會抽空修改。

簡介

  • 在iOS7中, 蘋果官方添加了新的類NSProgress。用來報告當前某個任務的進度, 或者多個任務的進度和這些任務的總進度等
  • NSProgress可以是一個樹狀結構, 一個節點進度可以有多個子節點, 每一個子節點只有一個父節點, 每一個子節點都有自己獨立的進度體系, 并且子節點完成任務后, 會將完成的數量反饋給父節點

基本屬性

  • totalUnitCount: 總單元, 用來記載某個任務的總單元數 (可以理解為某個任務正常結束時, 需要完成的任務總量)
  • completedUnitCount: 已完成單元數量, 記載某個任務執行過程中已經完成的單元數量 (可以理解為, 某一個任務在執行過程中已經完成的任務量)
  • fractionCompleted: 某個任務已完成單元量總單元量的比例
  • localizedDescription: 通過字符串的形式描述當前任務完成度
    • 格式: 100% completed
  • localizedAdditionalDescription: 同localizedDescription一樣, 用來描述當前任務的完成度
    • 格式: 9 of 10 (總任務量為10, 已完成任務量為9, 即 完成量/總量)

通過基本屬性和KVO, 創建簡單的單進度報告操作

#import "ViewController.h"

@interface ViewController ()

/** 進度 */
@property (nonatomic, strong) NSProgress *progress;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 初始化進度對象, 并設置進度總量
    self.progress = [NSProgress progressWithTotalUnitCount:10];
    
    //使用KVO觀察fractionCompleted的改變
    [self.progress addObserver:self forKeyPath:@"fractionCompleted" options:(NSKeyValueObservingOptionNew) context:nil];
}

/**
 當點擊根視圖時觸發
 */
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    //定時器
    NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(task:) userInfo:nil repeats:YES];
    //加到當前運行循環
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}

/**
 定時器調用的方法
 */
- (void)task:(NSTimer *)timer
{
    if (self.progress.completedUnitCount >= self.progress.totalUnitCount) {
        [timer invalidate];
        return;
    }
    self.progress.completedUnitCount += 1;
}

/**
 KVO回調方法
 */
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    //獲取觀察的新值
    CGFloat value = [change[NSKeyValueChangeNewKey] doubleValue];
    //打印
    NSLog(@"fractionCompleted --- %f, localizedDescription --- %@, localizedAdditionalDescription --- %@", value, self.progress.localizedDescription, self.progress.localizedAdditionalDescription);
}
@end
  • 當點擊控制器view的時候, 會有如下打印
進度打印.png

添加子節點的方法 (IOS9之前)

  • - (void)becomeCurrentWithPendingUnitCount:(int64_t)unitCount;
    • 該方法是用來在當前節點中分出一個子節點, 當子節點完成時, 總的進度完成量自動加上unitCount
    • unitCount: 子節點完成時, 總任務完成的量
  • - (void)resignCurrent;
    • - (void)becomeCurrentWithPendingUnitCount:(int64_t)unitCount;配套使用, 在這兩個方法中間需要設置至少一個子節點, 當子節點完成后, 總完成量會自動增加unitCount量, 如果不設置子節點, 那么總任務完成量直接增加unitCount
    • 注意點: 這兩個方法成對出現, 并且必須在同一個隊列中調用
  • 多節點示例代碼:
#import "ViewController.h"

//繼承自NSProgress的類, 添加一個唯一標識key
@interface LTProgress : NSProgress
/** 唯一標識 */
@property (nonatomic, copy) NSString *key;
@end
@implementation LTProgress
@end

@interface ViewController ()

/** 進度 */
@property (nonatomic, strong) NSProgress *progress;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 初始化進度對象, 并設置進度總量
    self.progress = [NSProgress progressWithTotalUnitCount:10];
    
    // 分出5個任務量給任務一
    [self.progress becomeCurrentWithPendingUnitCount:5];
    [self subTask:@"任務一"];
    [self.progress resignCurrent];
    
    // 分出5個任務量給任務二
    [self.progress becomeCurrentWithPendingUnitCount:5];
    [self subTask: @"任務二"];
    [self.progress resignCurrent];
}


- (void)subTask:(NSString *)key
{
    // 每個子任務的任務量分為10個單元, 每完成一個任務單元, 總任務完成量加上 5.0 / 10.0 = 0.5的任務單元
    LTProgress *subProgress = (LTProgress *)[LTProgress progressWithTotalUnitCount:10];
    subProgress.key = key;
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(task:) userInfo:@{@"subProgress" : subProgress} repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}

- (void)task:(NSTimer *)timer
{
    //獲取當前的進度
    NSDictionary *userInfo = timer.userInfo;
    LTProgress *subProgress = userInfo[@"subProgress"];
    //當完成量達到總量時停止任務
    if (subProgress.completedUnitCount >= subProgress.totalUnitCount) {
        [timer invalidate];
        return;
    }
    //模仿完成1份
    subProgress.completedUnitCount += 1;
    
    //打印
    NSLog(@"fractionCompleted --- %f, localizedDescription --- %@, localizedAdditionalDescription --- %@, key:%@", self.progress.fractionCompleted, self.progress.localizedDescription, self.progress.localizedAdditionalDescription, subProgress.key);
}

@end
  • 具體打印如下:
分出子任務.png
  • 當一個節點分出兩個子節點時, 給每個子節點分配了需要完成的單元量, 只要子節點完成, 總節點的進度就會自動增加對應的節點單元量, 而子節點又可以將這些分配到的單元量重新進行分配

iOS9中的優化

  • 在IOS9中新增了以下方法, 用來給節點添加子節點, 不用再使用上述兩個方法
    • + (NSProgress *)progressWithTotalUnitCount:(int64_t)unitCount parent:(NSProgress *)parent pendingUnitCount:(int64_t)portionOfParentTotalUnitCount
    • - (void)addChild:(NSProgress *)child withPendingUnitCount:(int64_t)inUnitCount
    • 可以通過這兩個方法直接分出子任務進度
  • 示例代碼:
- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 初始化進度對象, 并設置進度總量
    self.progress = [NSProgress progressWithTotalUnitCount:10];
    
    NSProgress *sub1 = [NSProgress progressWithTotalUnitCount:10 parent:self.progress pendingUnitCount:5];
    NSProgress *sub2 = [NSProgress progressWithTotalUnitCount:10 parent:self.progress pendingUnitCount:5];
    
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(task:) userInfo:@{@"sub1" : sub1, @"sub2" : sub2} repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}

- (void)task:(NSTimer *)timer
{
    //獲取當前的進度
    NSDictionary *userInfo = timer.userInfo;
    NSProgress *sub1 = userInfo[@"sub1"];
    NSProgress *sub2 = userInfo[@"sub2"];
    //當完成量達到總量時停止任務
    if (sub1.completedUnitCount >= sub1.totalUnitCount && sub2.completedUnitCount >= sub2.totalUnitCount) {
        [timer invalidate];
        return;
    }
    //模仿完成1份
    sub1.completedUnitCount += 1;
    NSLog(@"fractionCompleted --- %f, localizedDescription --- %@, localizedAdditionalDescription --- %@  --- sub1", self.progress.fractionCompleted, self.progress.localizedDescription, self.progress.localizedAdditionalDescription);
    
    sub2.completedUnitCount += 1;
    //打印
    NSLog(@"fractionCompleted --- %f, localizedDescription --- %@, localizedAdditionalDescription --- %@  --- sub2", self.progress.fractionCompleted, self.progress.localizedDescription, self.progress.localizedAdditionalDescription);
}
  • 控制臺打印:
iOS9分出子進度的打印.png
  • 屬性 and 方法
// 獲取當前線程的進度管理對象根節點
// 注意:當有NSProgress對象調用了becomeCurrentWithPendingUnitCount:方法后,這個方法才能獲取到
+ (nullable NSProgress *)currentProgress;

// 創建NSProgress對象, 并設置進度單元的總數
+ (NSProgress *)progressWithTotalUnitCount:(int64_t)unitCount;

// iOS9后使用 創建NSProgress對象, 并設置進度單元的總數
+ (NSProgress *)discreteProgressWithTotalUnitCount:(int64_t)unitCount NS_AVAILABLE(10_11, 9_0);

/**
   iOS9后使用, 創建一個NSProgress對象, 并指定父節點, 當完成時父節點完成單元數量 + portionOfParentTotalUnitCount

   @param unitCount                     任務進度的單元數量
   @param parent                        父節點, 可傳入nil
   @param portionOfParentTotalUnitCount 自身完成時, 父節點進度總量增加的值, 即父節點分配的任務量

   @return NSProgress對象, 是parent的子節點
 */
+ (NSProgress *)progressWithTotalUnitCount:(int64_t)unitCount parent:(NSProgress *)parent pendingUnitCount:(int64_t)portionOfParentTotalUnitCount NS_AVAILABLE(10_11, 9_0);

// iOS9后使用, 同上
- (instancetype)initWithParent:(nullable NSProgress *)parentProgressOrNil userInfo:(nullable NSDictionary *)userInfoOrNil NS_DESIGNATED_INITIALIZER;

// 注冊為當前線程根節點, 并分配子節點的任務量
- (void)becomeCurrentWithPendingUnitCount:(int64_t)unitCount;

// 取消注冊 與注冊方法必須同步出現, 必須在同一個線程內
- (void)resignCurrent;

// iOS9后使用, 向一個節點中添加一個子節點
- (void)addChild:(NSProgress *)child withPendingUnitCount:(int64_t)inUnitCount NS_AVAILABLE(10_11, 9_0);

#pragma mark *** Reporting Progress ***
// 進度單元總數
@property int64_t totalUnitCount;

// 進度單元已完成數
@property int64_t completedUnitCount;

// 描述進度 例如: 80% completed
@property (null_resettable, copy) NSString *localizedDescription;

// 描述進度 例如: 2 of 10 (2為已完成單元數量, 10為總單元數量)
@property (null_resettable, copy) NSString *localizedAdditionalDescription;

// 是否可以取消
@property (getter=isCancellable) BOOL cancellable;

// 是否可以暫停
@property (getter=isPausable) BOOL pausable;

// 是否已經取消
@property (readonly, getter=isCancelled) BOOL cancelled;

// 是否已經暫停
@property (readonly, getter=isPaused) BOOL paused;

// 進度取消回調
@property (nullable, copy) void (^cancellationHandler)(void);

// 進度暫停回調
@property (nullable, copy) void (^pausingHandler)(void);

// 進度恢復回調
@property (nullable, copy) void (^resumingHandler)(void) NS_AVAILABLE(10_11, 9_0);

#pragma mark *** Observing and Controlling Progress ***

@property (readonly, getter=isIndeterminate) BOOL indeterminate;

// 進度比例 0 - 1之間
@property (readonly) double fractionCompleted;

// 取消
- (void)cancel;

// 暫停
- (void)pause;

// 恢復
- (void)resume NS_AVAILABLE(10_11, 9_0);

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容