最近需要开发一个音乐下载的模块,考虑到方便封装和对象化,使用了NSOpreation

考虑到复用,我这里使用了继承NSOpreation的方法

1
2
3
4
5
6
7
8
9
@interface YOYMusicDownloadOperation : NSOperation
@property (copy, nonatomic) void(^processBlock)(double process);
@property (copy, nonatomic) void(^completeBlock)(BOOL r, NSURL *localUrl);

+ (NSString *)localMusicFilesPath;

- (instancetype)initWithURL:(NSURL *)url processCallback:(void(^)(double process))process complete:(void(^)(BOOL finised, NSURL *localUrl))complete;

@end

这个类很简单,两个block属性,一个用于进度监控,一个用于结束回调。

然后开始重写NSOpreation的两个方法:startmain

因为是下载需要异步回调,所以在函数main里使用了死循环,等下载完成后再结束线程。(如果线程结束,这个opreation会被释放)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
- (void)start
{
if ([self isCancelled]){

[self willChangeValueForKey:@"isFinished"];
finished = YES;
[self didChangeValueForKey:@"isFinished"];

return;
} else {

[self willChangeValueForKey:@"isExecuting"];
executing = YES;
[self main];
[self didChangeValueForKey:@"isExecuting"];
}
}

- (void)main
{
_session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
NSURLRequest *req = [NSURLRequest requestWithURL:self.downloadUrl];
_task = [_session downloadTaskWithRequest:req];

[_task resume];

while(_task != nil) {

[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];

}

[self willChangeValueForKey:@"isFinished"];
[self willChangeValueForKey:@"isExecuting"];
finished = YES;
executing = NO;
[self didChangeValueForKey:@"isFinished"];
[self didChangeValueForKey:@"isExecuting"];
}
- (BOOL) isFinished{
return(finished);
}

- (BOOL) isExecuting{
return(executing);
}

注意: 在重写NSOpreation时,注意也要重写 isFinishedisExecuting这两个属性的getter,同时还需要手动触发相关的KVO。如果没重写这两个方法,isFinishedisExecuting这两个属性会一直是NO,导致在线程完成后还留在NSOpreationQueue里,不会移除。

然后实现NSURLSession的几个代理方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{

_musicLocation = location;
}
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{

if(!error){
dispatch_sync_on_main_queue(^{
if(self.completeBlock)
{
if(_musicLocation)
{
self.completeBlock(YES, _musicLocation);
}
else
{
self.completeBlock(NO, nil);
}
}
});
}

_task = nil;
}
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
double process = totalBytesWritten/(double)totalBytesExpectedToWrite;

if(self.processBlock)
{
self.processBlock(process);
}
}

特别说明下,因为下载完成后需要刷新UI,我选择了在主线程调用completeBlock