下載管理器(OC)

0x00 寫在前面

  1. 需求是實(shí)現(xiàn)一個(gè)下載管理器(實(shí)現(xiàn)了斷點(diǎn)續(xù)傳功能)
    項(xiàng)目是寫過(guò)來(lái)個(gè)下載管理的類,更新界面使用的是KVO,使用起來(lái)有些繁瑣,邏輯看起來(lái)不太清晰,出問(wèn)題還不太好查找原因,所以就想著換一種思路實(shí)現(xiàn)

0x01 實(shí)現(xiàn)思路

  1. 用一個(gè)UITableView顯示下載的任務(wù)
  2. 自定義UITableViewCell界面,用于控制下載任務(wù)的狀態(tài)
  3. 每個(gè)下載任務(wù)都是一個(gè)Model,下載內(nèi)容變化時(shí)更新Model,通過(guò)Model更新UI界面

使用的算是MVVM模式,把下載的邏輯放到ViewModel中,以達(dá)到解耦效果

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];
                                                      //時(shí)間差
                                                      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 運(yùn)行效果圖和Demo地址

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

簡(jiǎn)單下載管理器.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,881評(píng)論 25 709
  • WebSocket-Swift Starscream的使用 WebSocket 是 HTML5 一種新的協(xié)議。它實(shí)...
    香橙柚子閱讀 24,725評(píng)論 8 183
  • 【皮膚是人體最大的器官,總面積1.5—2平米,皮膚整個(gè)體重5%—15%,厚度0.5—4毫米,表皮厚度0.07—2毫...
    xiaoxun小洵閱讀 1,938評(píng)論 0 2
  • 暑假的第六周,過(guò)的真是好快呀!過(guò)得這么快,都不知道自己在這一周里干什么呢?認(rèn)為,這周我還并沒(méi)有,恢復(fù)我的狀...
    百合花任澤欣閱讀 413評(píng)論 0 0
  • 每個(gè)周末,我都會(huì)和我媽視頻聊天。如果不找我媽要錢,我常常埋著頭,拿支筆在白紙上亂寫亂畫。然后找個(gè)理由,諸...
    乧吉閱讀 272評(píng)論 0 1

友情鏈接更多精彩內(nèi)容