RQDownloadOperation.m 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. //
  2. // RQDownloadOperation.m
  3. // TEST
  4. //
  5. // Created by 张嵘 on 2018/10/22.
  6. // Copyright © 2018 张嵘. All rights reserved.
  7. //
  8. #import "RQDownloadOperation.h"
  9. #import "RQDownloadHeader.h"
  10. #define kKVOBlock(KEYPATH,BLOCK)\
  11. [self willChangeValueForKey:KEYPATH];\
  12. BLOCK();\
  13. [self didChangeValueForKey:KEYPATH];
  14. @interface RQDownloadOperation (){
  15. BOOL _executing;
  16. BOOL _finished;
  17. }
  18. @property (nonatomic, strong) NSLock *lock;
  19. @property (nonatomic, assign) BOOL taskIsFinished;
  20. @end
  21. static const NSTimeInterval kTimeoutInterval = 60;
  22. static NSString * const kIsExecuting = @"isExecuting";
  23. static NSString * const kIsCancelled = @"isCancelled";
  24. static NSString * const kIsFinished = @"isFinished";
  25. @implementation RQDownloadOperation
  26. MJCodingImplementation
  27. - (instancetype)initWithDownloadModel:(RQDownloadModel *)downloadModel andSession:(NSURLSession *)session{
  28. self = [super init];
  29. if (self) {
  30. self.downloadModel = downloadModel;
  31. self.session = session;
  32. self.downloadModel.status = RQDownloadStatus_Waiting;
  33. }
  34. return self;
  35. }
  36. - (void)dealloc{
  37. NSLog(@"任务已销毁");
  38. }
  39. #pragma mark - Public Method
  40. - (void)startRequest{
  41. //已下载完成 || 任务未就绪 --> 则直接返回
  42. if (self.downloadModel.isFinished || !self.isReady) {
  43. return;
  44. }
  45. // 创建请求
  46. NSURL *url = [NSURL URLWithString:self.downloadModel.urlString];
  47. NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:kTimeoutInterval];
  48. // 设置请求头
  49. NSString *range = [NSString stringWithFormat:@"bytes=%d-", self.downloadModel.fileDownloadSize];
  50. [request setValue:range forHTTPHeaderField:@"Range"];
  51. if(!self.downloadTask){
  52. self.downloadTask = [self.session dataTaskWithRequest:request];
  53. }
  54. self.downloadTask.downloadModel = self.downloadModel;
  55. [self addObserver];
  56. [self.downloadTask resume];
  57. }
  58. // 进行检索获取Key
  59. - (BOOL)observerKeyPath:(NSString *)key observer:(id )observer{
  60. id info = self.downloadTask.observationInfo;
  61. NSArray *array = [info valueForKey:@"_observances"];
  62. for (id objc in array) {
  63. id Properties = [objc valueForKeyPath:@"_property"];
  64. id newObserver = [objc valueForKeyPath:@"_observer"];
  65. NSString *keyPath = [Properties valueForKeyPath:@"_keyPath"];
  66. if ([key isEqualToString:keyPath] && [newObserver isEqual:observer]) {
  67. return YES;
  68. }
  69. }
  70. return NO;
  71. }
  72. - (void)addObserver{
  73. if (![self observerKeyPath:@"state" observer:self]) {
  74. [self.downloadTask addObserver:self
  75. forKeyPath:@"state"
  76. options:NSKeyValueObservingOptionNew
  77. context:nil];
  78. }
  79. }
  80. - (void)removeObserver{
  81. if ([self observerKeyPath:@"state" observer:self]){
  82. [self.downloadTask removeObserver:self forKeyPath:@"state"];
  83. }
  84. }
  85. /** 挂起任务 */
  86. - (void)suspend{
  87. NSLog(@"%@: currentThread = %@", NSStringFromSelector(_cmd), [NSThread currentThread]);
  88. kKVOBlock(kIsExecuting, ^{
  89. [self.downloadTask suspend];
  90. _executing = NO;
  91. });
  92. }
  93. /** 开始执行任务 */
  94. - (void)startExcuting{
  95. NSLog(@"%@: currentThread = %@", NSStringFromSelector(_cmd), [NSThread currentThread]);
  96. kKVOBlock(kIsExecuting, ^{
  97. [self startRequest];
  98. _executing = YES;
  99. });
  100. }
  101. /** 开始执行任务 */
  102. - (void)resume{
  103. //等待中的任务交给队列调度
  104. if (self.downloadModel.status == RQDownloadStatus_Waiting)
  105. return;
  106. kKVOBlock(kIsExecuting, ^{
  107. [self startRequest];
  108. _executing = YES;
  109. });
  110. }
  111. /** 任务已完成 */
  112. - (void)completeOperation{
  113. [self willChangeValueForKey:kIsFinished];
  114. [self willChangeValueForKey:kIsExecuting];
  115. _executing = NO;
  116. _finished = YES;
  117. [self didChangeValueForKey:kIsExecuting];
  118. [self didChangeValueForKey:kIsFinished];
  119. }
  120. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
  121. if ([keyPath isEqualToString:@"state"]) {
  122. NSInteger newState = [[change objectForKey:@"new"] integerValue];
  123. NSInteger oldState = [[change objectForKey:@"old"] integerValue];
  124. switch (newState) {
  125. case NSURLSessionTaskStateSuspended:
  126. self.downloadModel.status = RQDownloadStatus_Suspended;
  127. //为进行任务管理 暂停任务后 直接取消
  128. [self cancel];
  129. break;
  130. case NSURLSessionTaskStateCompleted:{
  131. if (self.downloadModel.isFinished) {
  132. self.downloadModel.status = RQDownloadStatus_Completed;
  133. [self cancel];
  134. }else{
  135. if (self.downloadModel.status == RQDownloadStatus_Suspended) {
  136. }else{// 下载失败
  137. self.downloadModel.status = RQDownloadStatus_Failed;
  138. }
  139. }
  140. }break;
  141. case NSURLSessionTaskStateRunning:
  142. self.downloadModel.status = RQDownloadStatus_Running;
  143. break;
  144. case NSURLSessionTaskStateCanceling:
  145. self.taskIsFinished = YES;
  146. break;
  147. default:
  148. break;
  149. }
  150. if (newState != oldState) {
  151. if (self.downloadStatusChangedBlock) {
  152. self.downloadStatusChangedBlock();
  153. }
  154. }
  155. }
  156. }
  157. #pragma mark - Override Methods
  158. - (void)start{
  159. self.lock = [[NSLock alloc] init];
  160. [self.lock lock];
  161. //重写start方法时,要做好isCannelled的判断
  162. if ([self isCancelled]){
  163. //若已取消则设置状态已完成
  164. kKVOBlock(kIsFinished, ^{
  165. _finished = YES;
  166. });
  167. return;
  168. }
  169. kKVOBlock(kIsExecuting, ^{
  170. _executing = YES;
  171. });
  172. //未取消则调用main方法来执行任务
  173. //经测试 加入operationQueue中后会自动开启新的线程执行 无需手动开启
  174. [NSThread currentThread].name = self.downloadModel.downloadDesc;
  175. [NSThread mainThread].name = @"主线程";
  176. [self main];
  177. [self.lock unlock];
  178. }
  179. - (void)main{
  180. @try {
  181. // 必须为自定义的 operation 提供 autorelease pool,因为 operation 完成后需要销毁。
  182. @autoreleasepool {
  183. // 提供一个变量标识,来表示需要执行的操作是否完成了,当然,没开始执行之前,为NO
  184. _taskIsFinished = NO;
  185. //只有当没有执行完成和没有被取消,才执行自定义的相应操作
  186. if (self.taskIsFinished == NO && [self isCancelled] == NO) {
  187. [self startExcuting];
  188. }
  189. }
  190. }@catch (NSException * e) {
  191. NSLog(@"Exception %@", e);
  192. }
  193. }
  194. - (BOOL)isExecuting{
  195. return _executing;
  196. }
  197. - (BOOL)isFinished{
  198. return _finished;
  199. }
  200. - (BOOL)isAsynchronous{
  201. return YES;
  202. }
  203. /**
  204. * 1.cancel方法调用后 该operation将会取消并从queue中移除
  205. * 2.若队列中有等待中的任务,将会自动执行
  206. */
  207. - (void)cancel{
  208. BOOL isWaiting = self.downloadModel.status == RQDownloadStatus_Waiting;
  209. [self.downloadTask cancel];
  210. [self removeObserver];
  211. [super cancel];
  212. //等待状态下取消时 无需将isFinished设置为已完成 等调用start方法时检测canceled来设置
  213. //参考:https://blog.csdn.net/loggerhuang/article/details/50015573
  214. if (!isWaiting) {
  215. [self completeOperation];
  216. }
  217. }
  218. @end