1

I am making an OpenSource (github) helper class for downloading images asynchronously (I had major trouble with).

However, I have delegate methods set up to alert the delegate that a image has finished downloading. The problem is that the delegate method is not getting called. I am setting the delegate and everything, but I don't have a clue why the problem is occurring.

Please take a look at my code! I have only posted the relevant code.

MKAsyncImageDownloader.h

@protocol MKAsyncImageDownloaderDelegate <NSObject>
@required
- (void)imageShouldFinishDownloading;
@end
@interface MKAsyncImageDownloader : NSObject {
    id <MKAsyncImageDownloaderDelegate> delegate;
}
- (id) initWithDelegate:(id <MKAsyncImageDownloaderDelegate>) delegat;
@property (retain, nonatomic) id <MKAsyncImageDownloaderDelegate> delegate;
@end

MKAsyncImageDownloader.m

- (id) initWithDelegate:(id<MKAsyncImageDownloaderDelegate>) delegat {
    self = [super init];
    if (self) {
        delegate = delegat;
    }
    return self;
}
- (void)imageAtURLHasDownloaded:(NSDictionary *)dict {
    [downloadedImageArray addObject:[dict objectForKey:@"image"]];
    [[self delegate] imageShouldFinishDownloading];
}

MKOperation.m Subclass of NSOperation. I alloc/init MKAsynImageDownloader to perform the selector only. Code:

- (void)start {
    UIImage *image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:self.targetURL]];
    if (image) {
        NSDictionary *dict = [[NSDictionary alloc] initWithObjects:[NSArray arrayWithObjects:image, self.targetURL, nil] forKeys:[NSArray arrayWithObjects:@"image", @"url", nil]];
        MKAsyncImageDownloader *downloader = [[MKAsyncImageDownloader alloc] init];
        [downloader performSelectorOnMainThread:@selector(imageAtURLHasDownloaded:) withObject:dict waitUntilDone:YES];
        [dict release];
        [downloader release];
     }
    [image release];
}

RootViewController.h

MKAsyncImageDownloader *loader;

RootViewController.m Just to show how I am setting the delegate.

 loader = [[MKAsyncImageDownloader alloc] initWithDelegate:self];
max_
  • 24,076
  • 39
  • 122
  • 211
  • 1
    Not directly related to your issue, but your property retains the delegate, but your init assigns. Typically, "assign" is the right answer when dealing with delegates. – MarkPowell Apr 26 '11 at 01:08
  • Lastly, put a breakpoint in imageAtURLHasDownloaded and I can't imagine it would be that difficult to find the issue... – MarkPowell Apr 26 '11 at 01:08
  • I found the issue, and that is that the delegate is nil. I tried changing it to assign, but it made no difference. – max_ Apr 26 '11 at 01:10

2 Answers2

1

In your start method you are never calling your proper init method, you are calling:

MKAsyncImageDownloader *downloader = [[MKAsyncImageDownloader alloc] init];

you should be calling

MKAsyncImageDownloader *downloader = [[MKAsyncImageDownloader alloc] initWithDelegate:myDelegate];

Then you say you set a loader object somewhere else? These are two separate objects, the one you actually seem to use is what is referenced above.

That is:

loader = [[MKAsyncImageDownloader alloc] initWithDelegate:self];

does not make

MKAsyncImageDownloader *downloader = [[MKAsyncImageDownloader alloc] init];

work. Your MKOperation has no reference to your loader class that has the delegate set.

You state:

MKOperation.m Subclass of NSOperation. I alloc/init MKAsynImageDownloader to perform the selector only.

I think you misunderstand what you are doing here. You are create a brand new instance of MKAsynImageDownloader and performing the selector on that instance, not the loader instance that lives in your RootController. You probably want to have MKOperation take a MKAsyncImageDownloader object during it init.

EDIT:

This is the "downloader" I'm referring to. In your MKOperation's start

if (image) {
    NSDictionary *dict = [[NSDictionary alloc] initWithObjects:[NSArray arrayWithObjects:image, self.targetURL, nil] forKeys:[NSArray arrayWithObjects:@"image", @"url", nil]];
    -->> MKAsyncImageDownloader *downloader = [[MKAsyncImageDownloader alloc] init];
    [downloader performSelectorOnMainThread:@selector(imageAtURLHasDownloaded:) withObject:dict waitUntilDone:YES];
    [dict release];
    [downloader release];
 }

That is the separate instance from the one that lives in your RootViewController, this one does not have a delegate set. THIS is the one you are doing work on, therefore, this one is the one attempting to notify the delegate... but again, it doesn't have a delegate set.

MarkPowell
  • 16,482
  • 7
  • 61
  • 77
  • The MKOperation, NSOperation Subclass, has no need to be the delegate though. Nothing needs to be changed in the MKOperation file when it has downloaded an image. The RootViewController is the only class that needs to be set as a delegate. – max_ Apr 26 '11 at 01:15
  • But the MKOperation is the where the work is done! You create another instance, that instance does not have a delegate set. – MarkPowell Apr 26 '11 at 01:17
  • the delegate method is called in the MKOperation, but I need it to be called in the RootViewController, as the tableView needs to be updated. – max_ Apr 26 '11 at 01:21
  • loader != downloader, I don't know what else to tell you. – MarkPowell Apr 26 '11 at 01:23
  • loader.class != downloader.class!!! loader is in the RootViewController.h, and downloader is in the MKOperation, NSOperation subclass. – max_ Apr 26 '11 at 01:24
  • Ok I understand what you are saying, but I am trying to let the RootViewController know that an image has downloaded, not the MKOperation. In other words, I need to notify the RootViewController that the image has downloaded rather than notifying the MKOperation that an operation has finished within its class. – max_ Apr 26 '11 at 01:33
  • I understand. That's why you should pass a reference of your RootViewController's loader object to the MKOperation when you init it. Then in start don't create a new instance of MKASynImageDownloader, instead execute imageatURLHasDownloaded on the passed reference. – MarkPowell Apr 26 '11 at 01:37
  • thanks, I got it. But how can I get around having to set the delegate in the MKOperation? – max_ Apr 26 '11 at 01:55
  • You don't set it int there. Set it in you rootviewcontroller and pass THAT downloader to the operation object. – MarkPowell Apr 26 '11 at 01:56
1

Have you looked into SDWebImage?

Sometimes the easiest way to solve a problem is to use working code you don't have to maintain...

https://github.com/rs/SDWebImage

Kendall Helmstetter Gelner
  • 74,769
  • 26
  • 128
  • 150