iOS点点滴滴iOS Developer程序员

下载管理器(OC)

2016-06-16  本文已影响239人  LeeYZ

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
上一篇下一篇

猜你喜欢

热点阅读