Authored by DreamPiggy

Use the weak-strong dancing and the weak reference to manager instance to avoid …

…the leak of runningOperations
@@ -13,8 +13,9 @@ @@ -13,8 +13,9 @@
13 @interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation> 13 @interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>
14 14
15 @property (assign, nonatomic, getter = isCancelled) BOOL cancelled; 15 @property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
16 -@property (copy, nonatomic, nullable) SDWebImageNoParamsBlock cancelBlock; 16 +@property (strong, nonatomic, nullable) SDWebImageDownloadToken *downloadToken;
17 @property (strong, nonatomic, nullable) NSOperation *cacheOperation; 17 @property (strong, nonatomic, nullable) NSOperation *cacheOperation;
  18 +@property (weak, nonatomic, nullable) SDWebImageManager *manager;
18 19
19 @end 20 @end
20 21
@@ -125,6 +126,7 @@ @@ -125,6 +126,7 @@
125 } 126 }
126 127
127 __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new]; 128 __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
  129 + operation.manager = self;
128 __weak SDWebImageCombinedOperation *weakOperation = operation; 130 __weak SDWebImageCombinedOperation *weakOperation = operation;
129 131
130 BOOL isFailedUrl = NO; 132 BOOL isFailedUrl = NO;
@@ -150,8 +152,9 @@ @@ -150,8 +152,9 @@
150 if (options & SDWebImageQueryDiskSync) cacheOptions |= SDImageCacheQueryDiskSync; 152 if (options & SDWebImageQueryDiskSync) cacheOptions |= SDImageCacheQueryDiskSync;
151 153
152 operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key options:cacheOptions done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) { 154 operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key options:cacheOptions done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
153 - if (operation.isCancelled) {  
154 - [self safelyRemoveOperationFromRunning:operation]; 155 + __strong __typeof(weakOperation) strongOperation = weakOperation;
  156 + if (!strongOperation || strongOperation.isCancelled) {
  157 + [self safelyRemoveOperationFromRunning:strongOperation];
155 return; 158 return;
156 } 159 }
157 160
@@ -159,7 +162,7 @@ @@ -159,7 +162,7 @@
159 if (cachedImage && options & SDWebImageRefreshCached) { 162 if (cachedImage && options & SDWebImageRefreshCached) {
160 // If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image 163 // If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image
161 // AND try to re-download it in order to let a chance to NSURLCache to refresh it from server. 164 // AND try to re-download it in order to let a chance to NSURLCache to refresh it from server.
162 - [self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url]; 165 + [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
163 } 166 }
164 167
165 // download if no image or requested to refresh anyway, and download allowed by delegate 168 // download if no image or requested to refresh anyway, and download allowed by delegate
@@ -180,14 +183,16 @@ @@ -180,14 +183,16 @@
180 downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse; 183 downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
181 } 184 }
182 185
183 - SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {  
184 - __strong __typeof(weakOperation) strongOperation = weakOperation;  
185 - if (!strongOperation || strongOperation.isCancelled) { 186 + // `SDWebImageCombinedOperation` -> `SDWebImageDownloadToken` -> `downloadOperationCancelToken`, which is a `SDCallbacksDictionary` and retain the completed block bellow, so we need weak-strong again to avoid retain cycle
  187 + __weak typeof(strongOperation) weakSubOperation = strongOperation;
  188 + strongOperation.downloadToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
  189 + __strong typeof(weakSubOperation) strongSubOperation = weakSubOperation;
  190 + if (!strongSubOperation || strongSubOperation.isCancelled) {
186 // Do nothing if the operation was cancelled 191 // Do nothing if the operation was cancelled
187 // See #699 for more details 192 // See #699 for more details
188 // if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data 193 // if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data
189 } else if (error) { 194 } else if (error) {
190 - [self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url]; 195 + [self callCompletionBlockForOperation:strongSubOperation completion:completedBlock error:error url:url];
191 196
192 if ( error.code != NSURLErrorNotConnectedToInternet 197 if ( error.code != NSURLErrorNotConnectedToInternet
193 && error.code != NSURLErrorCancelled 198 && error.code != NSURLErrorCancelled
@@ -228,37 +233,27 @@ @@ -228,37 +233,27 @@
228 [self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil]; 233 [self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil];
229 } 234 }
230 235
231 - [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url]; 236 + [self callCompletionBlockForOperation:strongSubOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
232 }); 237 });
233 } else { 238 } else {
234 if (downloadedImage && finished) { 239 if (downloadedImage && finished) {
235 [self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil]; 240 [self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
236 } 241 }
237 - [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url]; 242 + [self callCompletionBlockForOperation:strongSubOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
238 } 243 }
239 } 244 }
240 245
241 if (finished) { 246 if (finished) {
242 - [self safelyRemoveOperationFromRunning:strongOperation]; 247 + [self safelyRemoveOperationFromRunning:strongSubOperation];
243 } 248 }
244 }]; 249 }];
245 - @synchronized(operation) {  
246 - // Need same lock to ensure cancelBlock called because cancel method can be called in different queue  
247 - operation.cancelBlock = ^{  
248 - [self.imageDownloader cancel:subOperationToken];  
249 - __strong __typeof(weakOperation) strongOperation = weakOperation;  
250 - [self safelyRemoveOperationFromRunning:strongOperation];  
251 - };  
252 - }  
253 } else if (cachedImage) { 250 } else if (cachedImage) {
254 - __strong __typeof(weakOperation) strongOperation = weakOperation;  
255 [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url]; 251 [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
256 - [self safelyRemoveOperationFromRunning:operation]; 252 + [self safelyRemoveOperationFromRunning:strongOperation];
257 } else { 253 } else {
258 // Image not in cache and download disallowed by delegate 254 // Image not in cache and download disallowed by delegate
259 - __strong __typeof(weakOperation) strongOperation = weakOperation;  
260 [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url]; 255 [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
261 - [self safelyRemoveOperationFromRunning:operation]; 256 + [self safelyRemoveOperationFromRunning:strongOperation];
262 } 257 }
263 }]; 258 }];
264 259
@@ -323,18 +318,6 @@ @@ -323,18 +318,6 @@
323 318
324 @implementation SDWebImageCombinedOperation 319 @implementation SDWebImageCombinedOperation
325 320
326 -- (void)setCancelBlock:(nullable SDWebImageNoParamsBlock)cancelBlock {  
327 - // check if the operation is already cancelled, then we just call the cancelBlock  
328 - if (self.isCancelled) {  
329 - if (cancelBlock) {  
330 - cancelBlock();  
331 - }  
332 - _cancelBlock = nil; // don't forget to nil the cancelBlock, otherwise we will get crashes  
333 - } else {  
334 - _cancelBlock = [cancelBlock copy];  
335 - }  
336 -}  
337 -  
338 - (void)cancel { 321 - (void)cancel {
339 @synchronized(self) { 322 @synchronized(self) {
340 self.cancelled = YES; 323 self.cancelled = YES;
@@ -342,10 +325,10 @@ @@ -342,10 +325,10 @@
342 [self.cacheOperation cancel]; 325 [self.cacheOperation cancel];
343 self.cacheOperation = nil; 326 self.cacheOperation = nil;
344 } 327 }
345 - if (self.cancelBlock) {  
346 - self.cancelBlock();  
347 - self.cancelBlock = nil; 328 + if (self.downloadToken) {
  329 + [self.manager.imageDownloader cancel:self.downloadToken];
348 } 330 }
  331 + [self.manager safelyRemoveOperationFromRunning:self];
349 } 332 }
350 } 333 }
351 334