-(void) setData:(NSDictionary *) dict
{
self.titleLabel.text = [ dict objectForKey:@"title" ];
self.dateLabel.text = [ dict objectForKey:@"pubDate" ];
self.URL = [ dict objectForKey:@"link" ];
NSMutableString *temp = [[NSMutableString alloc] initWithString:@"http://www.wai.de/emailsJPG/114x120/"];
[temp appendString:[dict objectForKey:@"description"]];
self.imgSrc = temp;
NSURL *imgUrl = [[NSURL alloc] init];
imgUrl = [NSURL URLWithString:@"http://www.wai.de/emailsJPG/114x120/372373.jpg"];
imageLoader = [[ImageLoader alloc] init];
[imageLoader loadImageFromURL:imgUrl withCallbackTarget:self.img withCallbackSelector:@selector(setupImage:)];
}
- (void) setupImage:(UIImage *) anImage
{
[img setImage:anImage];
}
*** -[ImageLoader urlToHashString:]: unrecognized selector sent to instance 0x489ad0
2008-10-17 18:00:25.257 WAIRSS1.2[4783:20b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[ImageLoader urlToHashString:]: unrecognized selector sent to instance 0x489ad0'
Also, you need to set up all of the instance variables in ImageLoader in the
loadImageFromURL:withCallbackTarget:withCallbackSelector: method, because imageURL, callbackTarget, and callbackSelector are all undefined when sendImageBack: is being called
@implementation ImageLoader
@synthesize callbackTarget;
@synthesize callbackSelector;
@synthesize imageURL;
static NSOperationQueue *imageLoadingQueue;
static NSMutableDictionary *imageCache;
static NSInteger identifierCounter;
- (void)loadImageFromURL:(NSURL *)anImageURL withCallbackTarget:(id)target withCallbackSelector:(SEL) selector
{
callbackTarget = target;
callbackSelector = selector;
imageURL = anImageURL;
NSLog(@"ImageLoader.m anImageURL = %@", imageURL);
identifierCounter = [self urlToHashString: imageURL];
DataLoaderOperation *op = [DataLoaderOperation queueDataLoadWithURL:imageURL withIdentifier:identifierCounter withCallbackTarget:self withCallbackSelector:@selector(sendImageBack:)];
[imageLoadingQueue addOperation:op];
}
+ (NSString*) urlToHashString:(NSURL*)aURL
{
return [NSString stringWithFormat:@"%U",[[aURL absoluteString] hash]];
}
- (NSData*) dataForURL:(NSURL*)aURL
{
NSLog(@"ImageLoader.m dataForURL %@", aURL);
return [imageCache valueForKey:[ImageLoader urlToHashString:aURL]];
}
- (void) addImageDataToCache:(NSData*)aDatum forURL:(NSURL*)aURL
{
NSLog(@"ImageLoader.m am adaugat in cache imaginea pentru url= %@", aURL);
[imageCache setValue:aDatum forKey:[ImageLoader urlToHashString:aURL]];
}
- (void) sendImageBack:(NSData*)data
{
if (!data) {
return;
}
[data retain];
[self addImageDataToCache:data forURL:imageURL];
UIImage *thisImage = [UIImage imageWithData:data];
if ([callbackTarget respondsToSelector:callbackSelector]) {
[callbackTarget performSelector:callbackSelector withObject:thisImage];
}
}
this is what appears in the debugger:
Code:*** -[ImageLoader urlToHashString:]: unrecognized selector sent to instance 0x489ad0 2008-10-17 18:00:25.257 WAIRSS1.2[4783:20b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[ImageLoader urlToHashString:]: unrecognized selector sent to instance 0x489ad0'
@interface ImageLoader: NSObject
{
id callbackTarget;
SEL callbackSelector;
NSURL *imageURL;
}
@property (assign) id callbackTarget;
@property (assign) SEL callbackSelector;
@property (nonatomic, retain) NSURL *imageURL;
- (void)loadImageFromURL:(NSURL *)anImageURL withCallbackTarget:(id)target withCallbackSelector:(SEL) selector;
- (void) sendImageBack:(NSData *) data;
- (void) addImageDataToCache:(NSData *)aData forURL:(NSURL *) aImageURL;
+ (NSString*) urlToHashString:(NSURL*)aURL;
- (NSData*) dataForURL:(NSURL*)aURL;
change the line:
identifierCounter = [self urlToHashString: imageURL];
You can start with just making it
identifierCounter = 0;
If that doesn't work, you need to identify the line that is throwing that error by putting NSLog()'s before/after all of them or use breakpoints.
- (void) sendImageBack:(NSData*)data
{
if (!data) {
return;
}
NSLog(@"send image back"); //added this to test if the function is called
[data retain];
[self addImageDataToCache:data forURL:imageURL];
UIImage *thisImage = [UIImage imageWithData:data];
if ([callbackTarget respondsToSelector:callbackSelector]) {
[callbackTarget performSelector:callbackSelector withObject:thisImage];
}
}
So put NSLog()'s in all of your functions and see what's getting hung up. Seems like standard debugging at this point I would say.
if (someObject == nil) {
NSLog(@"someObject exists");
}
NSLog(@"someObject's memory address: <%U>", someObject);
NSLog(@"About to add an op to imageLoadingQueue <%U>", imageLoadingQueue);
[imageLoadingQueue addOperation:op];
- (id) init
{
if (self = [super init]) {
if (imageCache == nil) {
identifierCounter = 0;
imageLoadingQueue = [[NSOperationQueue alloc] init];
[imageLoadingQueue setMaxConcurrentOperationCount:2];
}
}
return self;
}
Alright, I may see the problem.
Here's a tip when debugging, you can do
Code:if (someObject == nil) { NSLog(@"someObject exists"); }
You can also do:
Code:NSLog(@"someObject's memory address: <%U>", someObject);
Your imageCache object has not been initialized anywhere. So if you do:
Code:NSLog(@"About to add an op to imageLoadingQueue <%U>", imageLoadingQueue); [imageLoadingQueue addOperation:op];
You'll see in the log that imageLoadingQueue will show up as nil (NULL or 0)
So now add an init function in ImageLoader:
Code:- (id) init { if (self = [super init]) { if (imageCache == nil) { identifierCounter = 0; imageLoadingQueue = [[NSOperationQueue alloc] init]; [imageLoadingQueue setMaxConcurrentOperationCount:2]; } } return self; }
And when you run it again, your imageLoadingCache will have an address in the log line.
- (void) testCallbackSelector:(UIImage*)image
{
NSLog(@"testCallbackSelector received image with address <%U>", image);
}
Could you send me all of your example codes?
I don't have any example code really.
This person has some example code for doing an NSOperation
https://forums.macrumors.com/threads/571887/
And then I have modified and built on that as I explain in this thread.
Read and understand posts #1 through #19 of this thread and that should be enough to get you going
I'm struggling to come up with an efficient and performant way of loading remote images (over a local wifi network) to be used as images in a UITableViewCell.
What I'm basically trying to achieve is something similar to what the Apple iTunes Remote app does - when you browse a list of albums, it retrieves the artwork from the machine running iTunes and displays it on the left. The Remote app obviously does this asynchronously as the artwork doesn't appear immediately the first time you view a row.
Here's what I have so far: a custom UITableViewCell subclass that represents an Album; it has two properties, album name and album artwork URL, and two views, a UILabel and a UIImageView. When the album name is set, the UILabel is updated. This works just fine. When the artwork URL is set, the idea is that an asynchronous HTTP connection downloads the artwork an dupdates the UIImageView when its finished.
Here is my code for a custom AsyncArtworkFetcher class which, given a URL, downloads the artwork asynchronously and invokes a method on a delegate object when finished:
Code:@implementation AsyncArtworkFetcher @synthesize delegate; @synthesize url; @synthesize userData; - (void)fetch; { NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:5.0]; NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; if(connection) { receivedData = [[NSMutableData data] retain]; } } #pragma mark NSURLConnection Delegate Methods - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { [receivedData setLength:0]; } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { [receivedData appendData:data]; } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { UIImage *downloadedImage = [UIImage imageWithData:receivedData]; SEL delegateSelector = @selector(artworkFetcher:didFinish:); if([delegate respondsToSelector:delegateSelector]) { [delegate performSelector:delegateSelector withObject:self withObject:downloadedImage]; } [receivedData release]; [connection release]; } @end
And here's the relevant part of my custom table view cell class:
Code:- (void)setArtworkURL:(NSURL *)newURL; { if(newURL != artworkURL) { [artworkURL release]; artworkURL = [newURL retain]; AsyncArtworkFetcher *artworkFetcher = [[AsyncArtworkFetcher alloc] init]; artworkFetcher.url = artworkURL; artworkFetcher.delegate = self; [artworkFetcher fetch]; } } #pragma mark AsyncArtworkFetcher Delegate Methods - (void)artworkFetcher:(AsyncArtworkFetcher *)fetcher didFinish:(UIImage *)artworkImage; { albumArtView.image = artworkImage; [self setNeedsDisplay]; }
The main issue with this approach is down to the way in which UITableViewCell objects are reused. As you scroll through the list, when a cell is reused it will display the artwork for another album until its updated (its only briefly but it just looks wrong). One approach would be to have a unique cell for each album but this defeats the whole point of reusable cells.
Apart from this one issue, this works well enough in the simulator, but running on my actual iPhone it just locks the phone up, probably because of the number of asynchronous requests it's firing off at once. I guess I need to cache the downloaded image in some way (although I thought NSURLConnection dealt with caching automatically - perhaps I'm not configuring it correctly).
Any suggestions on a better way of going about this? Maybe I'm missing a really obvious technique - it must be possible, the Apple Remote app shows it can be done without any serious performance penalties.
- (void) sendImageBack:(NSData*)data
{
NSLog(@"Send image back function. Addres for data: <%U>", data);
if (!data) {
return;
}
NSLog(@"send image back"); //added this to test if the function is called
//[data retain];
NSLog(@"address for data = <%U>", data);
[self addImageDataToCache:data forURL:imageURL];
UIImage *thisImage = [UIImage imageWithData:data];
//UIImage *thisImage = [UIImage imageNamed:@"wai_news_logo_loading.png"];
//NSLog(@"Address for thisImage = <%U>", thisImage);
if ([callbackTarget respondsToSelector:callbackSelector]) {
NSLog(@"sendImageBack inside IF");
[callbackTarget performSelector:callbackSelector withObject:thisImage];
}
//NSLog(@"Address fot thisImage= <%U>", thisImage);
}
*** -[UIImageView testCallbackSelector:]: unrecognized selector sent to instance 0x4bb0a0
-(void) testCallbackSelector: (UIImage *) image
{
NSLog(@"testCallbackSelector received image with address <%U>", image);
}
[imageLoader loadImageFromURL:yourURL withCallbackTarget:yourObject withCallbackSelector:@selector(yourMethod:)];
#import <UIKit/UIKit.h>
#import "Feed.h"
#import "ImageLoader.h"
@interface FeedTableViewCell : UITableViewCell {
UILabel *titleLabel;
UILabel *dateLabel;
NSMutableString *URL;
NSMutableString *imgSrc;
UIImageView *img;
ImageLoader *imageLoader;
}
- (void) setData:(NSDictionary *) aFeed;
- (void) testCallbackSelector: (UIImage *) image;
- (void) setupImage:(UIImage *)anImage;
- (UILabel *) newLabelWithPrimaryColor:(UIColor *) primaryColor selectedColor: (UIColor *) selectedColor fontSize:(CGFloat) fontSIze bold:(BOOL) bold;
@property(nonatomic, retain) UILabel *titleLabel;
@property(nonatomic, retain) UILabel *dateLabel;
@property(nonatomic, retain) NSMutableString *URL;
@property(nonatomic, retain) NSMutableString *imgSrc;
@property(nonatomic, retain) UIImageView *img;
@property(nonatomic, retain) ImageLoader *imageLoader;
@end
#import "FeedTableViewCell.h"
#import "ImageLoader.h"
@implementation FeedTableViewCell
@synthesize titleLabel, dateLabel, URL, imgSrc, img, imageLoader;
...
-(void) setData:(NSDictionary *) dict
{
self.titleLabel.text = [ dict objectForKey:@"title" ];
self.dateLabel.text = [ dict objectForKey:@"pubDate" ];
self.URL = [ dict objectForKey:@"link" ];
NSMutableString *temp = [[NSMutableString alloc] initWithString:@"http://www.wai.de/emailsJPG/114x120/"];
[temp appendString:[dict objectForKey:@"description"]];
// clean up the link - get rid of spaces, returns, and tabs...
temp = [temp stringByReplacingOccurrencesOfString:@" " withString:@""];
temp = [temp stringByReplacingOccurrencesOfString:@"\n" withString:@""];
temp = [temp stringByReplacingOccurrencesOfString:@"\t" withString:@""];
temp = [[temp componentsSeparatedByString:@"<"] objectAtIndex:0];
NSURL *imgurl = [NSURL URLWithString: temp];
imageLoader = [[ImageLoader alloc] init];
//[imageLoader loadImageFromURL:imgurl withCallbackTarget:img withCallbackSelector:@selector(setupImage:)];
[imageLoader loadImageFromURL:imgurl withCallbackTarget:self.img withCallbackSelector:@selector(testCallbackSelector:)];
}
- (void) setupImage:(UIImage *) anImage
{
NSLog(@"Setup Image in table cell");
UIImage *loadImage = [[UIImage alloc] init];
loadImage = anImage;
[img setImage:loadImage];
}
-(void) testCallbackSelector: (UIImage *) image
{
NSLog(@"testCallbackSelector received image with address <%U>", image);
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @"MyIdentifier";
FeedTableViewCell *cell = (FeedTableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[FeedTableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
// Set up the cell
int storyIndex = [indexPath indexAtPosition: [indexPath length] - 1];
NSDictionary *itemAtIndex = (NSDictionary *)[stories objectAtIndex: storyIndex];
[cell setData:itemAtIndex];
return cell;
}
[imageLoader loadImageFromURL:imgurl withCallbackTarget:self.img withCallbackSelector:@selector(testCallbackSelector:)];
[imageLoader loadImageFromURL:imgurl withCallbackTarget:self withCallbackSelector:@selector(testCallbackSelector:)];
[imageLoader loadImageFromURL:imgurl withCallbackTarget:self withCallbackSelector:@selector(testCallbackSelector:)];
2008-11-17 19:17:38.661 Kookjij-Menus[12588:20b] ImageLoader.m anImageURL = http://static.kookjij.nl//upload//0000/7876/00007876-60x60.jpg
2008-11-17 19:17:38.664 Kookjij-Menus[12588:20b] *** -[ImageLoader urlToHashString:]: unrecognized selector sent to instance 0x3824d0
-(void)setData:(NSDictionary *)dict {
self.titleLabel.text = [dict objectForKey:@"naam"];
self.urlLabel.text = [dict objectForKey:@"description"];
self.itemID = (int)[dict objectForKey:@"id"];
NSURL *imgUrl = [[NSURL alloc] init];
imgUrl = [NSURL URLWithString:[dict objectForKey:@"image"]];
imageLoader = [[ImageLoader alloc] init];
[imageLoader loadImageFromURL:imgUrl withCallbackTarget:self withCallbackSelector:@selector(setupImage:)];
}
- (void) setupImage:(UIImage *) anImage
{
NSLog(@"Setup Image in table cell");
UIImage *loadImage = [[UIImage alloc] init];
loadImage = anImage;
[imageView setImage:loadImage];
}