소스 검색

Fix the issue that "There may be no complete callback when download the picture of the local path"

DreamPiggy 6 년 전
부모
커밋
482d45dc65
1개의 변경된 파일17개의 추가작업 그리고 10개의 파일을 삭제
  1. 17 10
      SDWebImage/SDWebImageDownloader.m

+ 17 - 10
SDWebImage/SDWebImageDownloader.m

@@ -293,6 +293,7 @@
     }
     
     LOCK(self.operationsLock);
+    id downloadOperationCancelToken;
     NSOperation<SDWebImageDownloaderOperationInterface> *operation = [self.URLOperations objectForKey:url];
     // There is a case that the operation may be marked as finished or cancelled, but not been removed from `self.URLOperations`.
     if (!operation || operation.isFinished || operation.isCancelled) {
@@ -308,22 +309,28 @@
             UNLOCK(sself.operationsLock);
         };
         [self.URLOperations setObject:operation forKey:url];
+        // Add the handlers before submitting to operation queue, avoid the race condition that operation finished before setting handlers.
+        downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
         // Add operation to operation queue only after all configuration done according to Apple's doc.
         // `addOperation:` does not synchronously execute the `operation.completionBlock` so this will not cause deadlock.
         [self.downloadQueue addOperation:operation];
-    }
-    else if (!operation.isExecuting) {
-        if (options & SDWebImageDownloaderHighPriority) {
-            operation.queuePriority = NSOperationQueuePriorityHigh;
-        } else if (options & SDWebImageDownloaderLowPriority) {
-            operation.queuePriority = NSOperationQueuePriorityLow;
-        } else {
-            operation.queuePriority = NSOperationQueuePriorityNormal;
+    } else {
+        // When we reuse the download operation to attach more callbacks, there may be thread safe issue because the getter of callbacks may in another queue (decoding queue or delegate queue)
+        // So we lock the operation here, and in `SDWebImageDownloaderOperation`, we use `@synchonzied (self)`, to ensure the thread safe between these two classes.
+        @synchronized (operation) {
+            downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
+        }
+        if (!operation.isExecuting) {
+            if (options & SDWebImageDownloaderHighPriority) {
+                operation.queuePriority = NSOperationQueuePriorityHigh;
+            } else if (options & SDWebImageDownloaderLowPriority) {
+                operation.queuePriority = NSOperationQueuePriorityLow;
+            } else {
+                operation.queuePriority = NSOperationQueuePriorityNormal;
+            }
         }
     }
     UNLOCK(self.operationsLock);
-
-    id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
     
     SDWebImageDownloadToken *token = [SDWebImageDownloadToken new];
     token.downloadOperation = operation;