下载管理器(OC)

0x00 写在前面

  1. 需求是实现一个下载管理器(实现了断点续传功能)
    项目是写过来个下载管理的类,更新界面使用的是KVO,使用起来有些繁琐,逻辑看起来不太清晰,出问题还不太好查找原因,所以就想着换一种思路实现

0x01 实现思路

  1. 用一个UITableView显示下载的任务
  2. 自定义UITableViewCell界面,用于控制下载任务的状态
  3. 每个下载任务都是一个Model,下载内容变化时更新Model,通过Model更新UI界面

使用的算是MVVM模式,把下载的逻辑放到ViewModel中,以达到解耦效果

0x10 代码展示

1.控制器代码

@interface ViewController ()<UITableViewDataSource, UITableViewDelegate>
@property (weak, nonatomic) IBOutlet UITableView *tableView;

@property (nonatomic, strong) NSMutableArray *downloadModels;
@end

@implementation ViewController

#pragma mark - Lifecycle
- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self setDefault];
}

#pragma mark - Method
- (void)setDefault {
    self.downloadModels = [[NSMutableArray array] mutableCopy];
    
    NSString *url = @"http://dldir1.qq.com/qqfile/QQforMac/QQ_V4.0.6.dmg";
    for (int i = 0; i < 20 ; i++) {
        NSString *fileName = [NSString stringWithFormat:@"qq_%d.dmg",i];
        DownloadModelItem *downloadModel = [[DownloadModelItem alloc] initWithDownloadUrlStr:url andSaveFilePath:kCachePath fileName:fileName];
        [self.downloadModels addObject:downloadModel];
    }
}

#pragma mark - UITableView
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.downloadModels.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    DownloadCell *cell = [DownloadCell cellWithTableView:tableView];
    cell.downloadModel = [self.downloadModels objectAtIndex:indexPath.row];
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
}

@end

2.ViewModel代码

@interface ZZBaseDownloadModel ()
@property (nonatomic, strong) NSString *urlStr;
@property (nonatomic, strong) NSString *fileName;
@property (nonatomic, strong) NSString *saveFilePath;
@property (nonatomic, assign) DownloadStatus status;

@property (nonatomic, assign) long long totalExpectedToRead;
@property (nonatomic, assign) long long totalRead;
@property (nonatomic, assign) NSUInteger bytesRead;
@property (nonatomic, assign) float progress;
@property (nonatomic, assign) NSUInteger totalBytes;
@property (nonatomic, assign) long long byteSpeed;
@property (nonatomic, strong) NSDate *lastReadDate;

@property (nonatomic, strong) ZZDownloadRequest *request;
@end

@implementation ZZBaseDownloadModel

- (instancetype)initWithDownloadUrlStr:(NSString *)urlStr
                       andSaveFilePath:(NSString *)saveFilePath
                              fileName:(NSString *)fileName {
    self = [super init];
    if (self) {
        self.urlStr = urlStr;
        self.fileName = fileName;
        self.saveFilePath = saveFilePath;
        self.status = DownloadStatusNormal;
    }
    return self;
}

#pragma mark - Property Method
- (NSString *)urlStr {
    return _urlStr;
}

- (NSString *)fileName {
    return _fileName;
}

- (NSString *)saveFilePath {
    return _saveFilePath;
}

- (DownloadStatus)status {
    return _status;
}

- (long long)totalExpectedToRead {
    return _totalExpectedToRead;
}

- (long long)totalRead {
    return _totalRead;
}

- (NSUInteger)bytesRead {
    return _bytesRead;
}

- (float)progress {
    return _progress;
}

- (NSString *)speed {
    NSString *speed = self.byteSpeed == 0 ? @"0 KB" : [NSByteCountFormatter stringFromByteCount:self.byteSpeed countStyle:NSByteCountFormatterCountStyleFile];
    return [NSString stringWithFormat:@"%@/s", speed];
}

- (NSDate *)lastReadDate {
    if (!_lastReadDate) {
        _lastReadDate = [NSDate date];
    }
    return _lastReadDate;
}

#pragma mark - Private Method
- (BOOL)checkUrlAndSavePathEmpty {
    BOOL isEmpty = NO;
    isEmpty = [self.urlStr isEmpty];
    isEmpty = isEmpty || [self.saveFilePath isEmpty];
    return isEmpty;
}

- (void)doDownload {
    __weak typeof(self) WS = self;
    self.request = [ZZDownloadRequest downloadFileWithURLString:self.urlStr
                                                   downloadPath:self.saveFilePath
                                                       fileName:self.fileName
                                                  progressBlock:^(float progress, NSUInteger bytesRead, unsigned long long totalRead, unsigned long long totalExpectedToRead) {
                                                      WS.totalBytes += bytesRead;
                                                      NSDate *currentDate = [NSDate date];
                                                      //时间差
                                                      double time = [currentDate timeIntervalSinceDate:WS.lastReadDate];
                                                      if (time >= 1) {
                                                          long long speed = WS.totalBytes * 1.0 / time;
                                                          WS.byteSpeed = speed;
                                                          WS.totalBytes = 0.0;
                                                          WS.lastReadDate = currentDate;
                                                      }
                                                      WS.progress = progress;
                                                      WS.bytesRead = bytesRead;
                                                      WS.totalRead = totalRead;
                                                      WS.totalExpectedToRead = totalExpectedToRead;
                                                      if (WS.progressBlock) {
                                                          WS.progressBlock(progress, bytesRead, totalRead, totalExpectedToRead);
                                                      }
                                                  }
                                                   successBlock:self.successBlock
                                                    cancelBlock:self.cancelBlock
                                                   failureBlock:self.failureBlock];
    if (self.request) {
        self.status = DownloadStatusDownloading;
    }
}

- (void)resetInfo {
    self.byteSpeed = 0;
}

#pragma mark - Public Method
- (void)start {
    if ([self checkUrlAndSavePathEmpty]) {
        self.status = DownloadStatusFailed;
        return;
    }
    if (self.status == DownloadStatusFinished || self.status == DownloadStatusDownloading) {
        return;
    }
    [self doDownload];
}

- (void)pause {
    if (self.status == DownloadStatusDownloading) {
        [self.request pauseDownload];
    }
    self.status = DownloadStatusPause;
    [self resetInfo];
}

- (void)cancel {
    self.status = DownloadStatusWait;
    if (self.request == nil) {
        return;
    }
    [self.request cancelDownload];
    [self resetInfo];
}

@end

3.View代码

@interface DownloadCell ()
@property (weak, nonatomic) IBOutlet UILabel *nameLabel;
@property (weak, nonatomic) IBOutlet UILabel *infoLabel;
@property (weak, nonatomic) IBOutlet UILabel *speedLabel;
@property (weak, nonatomic) IBOutlet UIProgressView *progressBar;
@property (weak, nonatomic) IBOutlet UIButton *downloadOptBtn;
@end

@implementation DownloadCell

#pragma mark - Lifycycle
- (void)awakeFromNib {
    [super awakeFromNib];
}

#pragma mark - Property Method
- (void)setDownloadModel:(DownloadModelItem *)downloadModel {
    _downloadModel = downloadModel;
    
    self.nameLabel.text = downloadModel.fileName;
    self.progressBar.progress = downloadModel.progress;
    [self setCellInfoWithStatus:downloadModel.status];
    
    __weak typeof(self) WS = self;
    downloadModel.progressBlock = ^(float progress, NSUInteger bytesRead, unsigned long long totalRead, unsigned long long totalExpectedToRead) {
        WS.progressBar.progress = progress;
        [WS setSpeedWithSpeedStr:WS.downloadModel.speed];
    };
    downloadModel.successBlock = ^(ZZDownloadRequest *request, id responseObject) {
        [WS setCellInfoWithStatus:DownloadStatusFinished];
        [WS setSpeedWithSpeedStr:ZeroSpeedString];
    };
    downloadModel.cancelBlock = ^(ZZDownloadRequest *request) {
        [WS setCellInfoWithStatus:DownloadStatusCancel];
        [WS setSpeedWithSpeedStr:ZeroSpeedString];
    };
    downloadModel.failureBlock = ^(ZZDownloadRequest *request, NSError *error) {
        [WS setCellInfoWithStatus:DownloadStatusFailed];
        [WS setSpeedWithSpeedStr:ZeroSpeedString];
    };
}

#pragma mark - Private Method
- (void)setCellInfoWithStatus:(DownloadStatus)status {
    NSString *introStr = @"下载";
    NSString *btnTitle = @"下载";
    switch (status) {
        case DownloadStatusNormal:
            break;
        case DownloadStatusWait:
            introStr = @"等待下载";
            btnTitle = @"等待";
            break;
        case DownloadStatusDownloading:
            introStr = @"正在下载...";
            btnTitle = @"暂停";
            break;
        case DownloadStatusPause:
            introStr = @"暂停下载";
            btnTitle = @"下载";
            break;
        case DownloadStatusCancel:
            introStr = @"取消下载";
            btnTitle = @"下载";
            break;
        case DownloadStatusFinished:
            introStr = @"下载完成";
            btnTitle = @"已完成";
            break;
        case DownloadStatusFailed:
            introStr = @"下载失败";
            btnTitle = @"重试";
            break;
    }
    self.infoLabel.text = introStr;
    [self.downloadOptBtn setTitle:btnTitle forState:UIControlStateNormal];
    self.speedLabel.text = self.downloadModel.speed;
}

- (void)setSpeedWithSpeedStr:(NSString *)speed {
    self.speedLabel.text = speed;
}

#pragma mark - Public Method
+ (instancetype)cellWithTableView:(UITableView *)tableView {
    static NSString *CellID = @"DownloadCellID";
    DownloadCell *cell = [tableView dequeueReusableCellWithIdentifier:CellID];
    if (!cell) {
        cell = [[[NSBundle mainBundle] loadNibNamed:@"DownloadCell" owner:nil options:nil] lastObject];
    } else {
        cell.downloadModel.progressBlock = nil;
        cell.downloadModel.successBlock = nil;
        cell.downloadModel.cancelBlock = nil;
        cell.downloadModel.failureBlock = nil;
    }
    return cell;
}

#pragma mark - Action
- (IBAction)OnDownloadOptBtnTap:(UIButton *)sender {
    switch (self.downloadModel.status) {
        case DownloadStatusNormal:
            case DownloadStatusPause:
            case DownloadStatusCancel:
            case DownloadStatusFailed:
            [self.downloadModel start];
            break;
        case DownloadStatusWait:
        case DownloadStatusDownloading:
            [self.downloadModel pause];
            break;
        case DownloadStatusFinished:
            break;
    }
    [self setCellInfoWithStatus:self.downloadModel.status];
}

@end

0x11 运行效果图和Demo地址

DEMO地址: https://github.com/LeeYZ/DownloadManager

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

推荐阅读更多精彩内容