6

I am trying to eager load bunch of images:

for (NSDictionary *s in things) {
    [manager downloadWithURL:[NSURL URLWithString:s[photo]]
                     options:0
                    progress:nil
                   completed:nil];
}

It's not downloading these images. However, if I pass in an empty completion block, like so:

for (NSDictionary *s in things) {
    [manager downloadWithURL:[NSURL URLWithString:s[photo]]
                     options:0
                    progress:nil
                   completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished) { }];
}

then it works just fine. My question is: why? Is there a better way to do this? Passing in an empty block doesn't seem right to me.

0xSina
  • 20,973
  • 34
  • 136
  • 253

3 Answers3

19

The API you are using is not the correct one.

To prefetch images and store them in cache, use SDWebImagePrefetcher which is meant for that.

NSMutableArray * urls = [NSMutableArray arrayWithCapacity:things.count];
for (NSDictionary *s in things) {
    [urls addObject:[NSURL URLWithString:s[photo]]];
}
[[SDWebImagePrefetcher sharedImagePrefetcher] prefetchURLs:urls];

As a side note I submitted a pull request - which has just been merged - to enforce the presence of a completedBlock in the API you are (mis)using, so that other programmers won't fall you in your same mistake.

Gabriele Petronella
  • 106,943
  • 21
  • 217
  • 235
  • 5
    FYI, if you're doing this multiple times without the completion blocks you might not want to use the `sharedImagePrefetcher`, as it will cancel the currently running prefetch operations before doing the new ones. I alloc a new one instead of using the shared one in my code. – Enrico Susatyo Jan 05 '16 at 09:33
  • @EnricoSusatyo that's huge. Thanks for clearing that up. – kevinl Dec 22 '16 at 17:55
  • The API is flawed. The prefetcher is all or nothing at once. It's not always possible (or convenient) to batch up a bunch of requests in advance. There's no reason why this shared manager can't accept additional prefetch requests without blowing away any previously-enqeued ones. Having to init individual prefetchers is odd. – CIFilter May 02 '17 at 22:01
1

If you look carefully at the -[SDWebImageManager downloadWithURL:options:progress:completed:] implementation, you'll find the lines:

if (!url || !completedBlock || (!(options & SDWebImageRetryFailed) && isFailedUrl))
{
    if (completedBlock)
    {
        // Complain about invalid URL, completely irrelevant to us at this point.
        ...
    }
    return operation;
}

So yes, it does nothing if completionBlock is nil. Why? Probably, the SDWebImage developers considered that method useless without that parameter passed. You'd better create a GitHub issue to ask them.

iHunter
  • 6,205
  • 3
  • 38
  • 56
  • 1
    It makes sense. If you are not doing anything with the image, why are you using it in the first place? They should use a parameter assertion to enforce this though. – Gabriele Petronella Aug 29 '13 at 11:37
  • 1
    @GabrielePetronella I agree, but I 0xSina's way of using it is completely natural. It's a design flaw. – iHunter Aug 29 '13 at 11:40
  • They should raise an exception to inform the developer. I'm making a pull request. – Gabriele Petronella Aug 29 '13 at 11:43
  • @GabrielePetronella I want to eager load. I have a tableview with bunch of images, 90% of them are not displayed until the user scrolls down. I want to preemptively load them. Obviously, I don't need the images YET, so that's why I don't need the block. I just want to make sure the images are loaded. – 0xSina Aug 29 '13 at 12:07
  • @GabrielePetronella I guess what I am asking is, if you wanted to eager load images (make sure they are downloaded and cached), how would you do it? – 0xSina Aug 29 '13 at 12:08
1

SDWebImage has fixed the completion block issue and this is now possible with a single line of Swift:

SDWebImagePrefetcher.shared().prefetchURLs(urlArray)
Justin Vallely
  • 5,932
  • 3
  • 30
  • 44