Use the weak-strong dancing and the weak reference to manager instance to avoid …
…the leak of runningOperations
Showing
1 changed file
with
21 additions
and
38 deletions
@@ -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 |
-
Please register or login to post a comment