进度: 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);

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容