This is one of my first times trying to write my own delegates. I am using iOS5 Beta 7 and writing my delegate function.
What it should do is that a table (MyTableController
) loads a list of TV Channels. This is a dictionary containing the key "logotype", which is a URL to the logotype of the image.
I created my class called "Channel
" and my delegate called "ChannelDelegate
". Now, when my table does cellForRowAtIndexPath
it initiates my Channel class and calls the function getChannelImageForChannelId:externalRefference:indexPath
. So far so good, now my channel class check if the file exists locally, if not it will download it (using ASIHttpRequest
). Also, so far so good. Now when ASIHttpRequest returns on requestDidFinish it dies after a few results. I notice that the file did get downloaded because after a few tries it works like a charm, hence my delegate seems to work.
This is my code:
The CellForRowAtIndexPath:- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"CurrentChannelInfoCell";
CurrentChannelInfoCell *cell = (CurrentChannelInfoCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil){
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:CellIdentifier owner:nil options:nil];
for(id currentObject in topLevelObjects)
{
if([currentObject isKindOfClass:[CurrentChannelInfoCell class]])
{
cell = (CurrentChannelInfoCell *)currentObject;
break;
}
}
}
NSArray *keys = [self.content allKeys];
id channelId = [keys objectAtIndex:indexPath.row];
NSDictionary *channel = [self.content objectForKey:channelId];
int cId = [(NSString*)channelId intValue];
[cell.textLabel setText:[channel objectForKey:@"name"]];
[cell.channelImage setHidden:YES];
Channel *theChannel = [[Channel alloc] init];
[theChannel setDelegate:self];
[theChannel getChannelImageForChannelId:cId externalRefference:[channel objectForKey:@"logotype"] indexPath:indexPath];
return cell;
}
- (void)didRecieveImageForChannel:(NSString*)imagePath indexPath:(NSIndexPath*)indexPath
{
NSLog(@"Did recieve the it.!");
}
My Delegate, ChannelDelegate.h
:
#import <Foundation/Foundation.h>
@class Channel;
@protocol ChannelDelegate <NSObject>
@optional
- (void)didRecieveImageForChannel:(NSString*)imagePath indexPath:(NSIndexPath*)indexPath;
@end
Channel.h
:
开发者_JAVA技巧#import <Foundation/Foundation.h>
#import "ChannelDelegate.h"
#import "Reachability.h"
#import "ASIHTTPRequest.h"
#import "ASINetworkQueue.h"
#import "ASIFormDataRequest.h"
@interface Channel : NSOperation <NSObject, ASIHTTPRequestDelegate>
{
ASINetworkQueue *networkQueue;
// Called on the delegate (if implemented) when the request completes successfully.
id <ChannelDelegate> delegate;
SEL didRecieveImageForChannelSelector;
}
@property (strong, nonatomic) id delegate;
@property (assign) SEL didRecieveImageForChannelSelector;
- (void)getChannelImageForChannelId:(int)channelId externalRefference:(NSString*)url indexPath:(NSIndexPath*)indexPath;
- (id)delegate;
@end
Channel.m
:
#import "Channel.h"
#import <objc/runtime.h>
@implementation Channel
static char kAssociationKey;
@synthesize didRecieveImageForChannelSelector;
@synthesize delegate;
- (void)getChannelImageForChannelId:(int)channelId externalRefference:(NSString*)url indexPath:(NSIndexPath*)indexPath
{
[self setDidRecieveImageForChannelSelector:@selector(didRecieveImageForChannel:indexPath:)];
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *imagePath = [documentsPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%d.gif", channelId]];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:imagePath];
if (fileExists)
{
NSLog(@"Downloaded image!");
[[self delegate] performSelector:[self didRecieveImageForChannelSelector] withObject:imagePath withObject:indexPath];
}
else {
NSLog(@"Need to fetch it.!");
NSURL *theUrl = [NSURL URLWithString:url];
ASINetworkQueue *newQueue = [[ASINetworkQueue alloc] init];
[newQueue setRequestDidFinishSelector:@selector(channelImageFetched:)];
[newQueue setRequestDidFailSelector:@selector(processFailed:)];
[newQueue setDelegate:self];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:theUrl];
[request setTimeOutSeconds:60];
[request setDownloadDestinationPath:imagePath];
[newQueue addOperation:request];
objc_setAssociatedObject(request, &kAssociationKey, indexPath, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[newQueue go];
}
}
- (id)delegate
{
id d = delegate;
return d;
}
- (void)setDelegate:(id)newDelegate
{
delegate = newDelegate;
}
// Handle process two
- (void)channelImageFetched:(ASIHTTPRequest *)request
{
NSLog(@"channelImageFetched!");
NSIndexPath *indexPath = objc_getAssociatedObject(request, &kAssociationKey);
NSString *imagePath = request.downloadDestinationPath;
[[self delegate] performSelector:[self didRecieveImageForChannelSelector] withObject:imagePath withObject:indexPath];
NSLog(@"File downloaded!");
}
- (void) processFailed:(ASIHTTPRequest *)request
{
NSError *error = [request error];
UIAlertView *errorView;
errorView = [[UIAlertView alloc]
initWithTitle: NSLocalizedString(@"Whoops!", @"Errors")
// message: NSLocalizedString(@"An error occured while preforming the request. Please try again.", @"Network error")
message: [error localizedDescription]
delegate: self
cancelButtonTitle: NSLocalizedString(@"Close", @"Errors") otherButtonTitles: nil];
[errorView show];
/* NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:
[request error], @"Error",
[request url], @"URL",
[[NSDate alloc] init], @"timestamp", nil];
[FlurryAPI logEvent:@"APNS-Could not fetch data." withParameters:dictionary timed:YES];
*/
}
@end
The error I recieve seems to differ from time to time, however these seem the errors I got:
2011-09-26 13:11:57.605 TVSports[4541:ef03] Done!
2011-09-26 13:11:57.609 TVSports[4541:ef03] Downloaded image! 2011-09-26 13:11:57.610 TVSports[4541:ef03] Did recieve the it! 2011-09-26 13:11:57.613 TVSports[4541:ef03] Need to fetch if.! 2011-09-26 13:11:57.616 TVSports[4541:ef03] Need to fetch the it.! 2011-09-26 13:11:57.618 TVSports[4541:ef03] Need to fetch the it.! 2011-09-26 13:11:57.621 TVSports[4541:ef03] Need to fetch the it.! 2011-09-26 13:11:57.624 TVSports[4541:ef03] Need to fetch the it.! 2011-09-26 13:11:57.629 TVSports[4541:ef03] Need to fetch the it.! 2011-09-26 13:11:57.633 TVSports[4541:ef03] Need to fetch the it.! 2011-09-26 13:11:57.663 TVSports[4541:ef03] Need to fetch the it.! 2011-09-26 13:11:57.669 TVSports[4541:ef03] Need to fetch the it.! 2011-09-26 13:11:57.846 TVSports[4541:ef03] -[UIDeviceWhiteColor channelImageFetched:]: unrecognized selector sent to instance 0x65a1e30 2011-09-26 13:11:57.847 TVSports[4541:ef03] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIDeviceWhiteColor channelImageFetched:]: unrecognized selector sent to instance 0x65a1e30' * First throw call stack: (0x15da272 0x1769ce6 0x15dbf0d 0x1540e2f 0x1540c12 0x15dc092 0x3adf8 0x15dc092 0x24c43 0x15dc092 0xef9fac 0x15aec0f 0x15118c3 0x15111a4 0x1510b04 0x1510a1b 0x1ce6f67 0x1ce702c 0x608cf2 0x2718 0x2675 0x1) terminate called throwing an exception(gdb)
Or I get an EXC_BAD_ACCESS on a row in ASIHTTPRequest (which I can't reproduce the last 10 tries.)
Can anybody seem what I am doing wrong? Where is my code all screwed up?
[UIDeviceWhiteColor channelImageFetched:]: unrecognized selector sent to instance 0x65a1e30
-- What this is telling you is that the "channelImageFetched" "message" was "sent" to an object of type UIDeviceWhiteController. (Ie, UIIDeviceWhiteController ->channelImageFetched" was called.) And UIDeviceWhiteController has no method named "channelImageFetched".
While this could be a problem where you're getting your pointers mixed up, more likely the object (your object) that DOES implement "channelImageFetched" was over-released and deallocated (you can confirm this by placing an NSLog in your dealloc routine.) So your problem is most likely a common storage management bug.
Understanding storage management with delegates is a hair tricky. Generally, when an object is handed a delegate object it should retain it and keep it retained until all possible delegate methods have been called, at which point that first object should release the delegate object. But of course its your responsibility to keep the delegate object retained before setting it as a delegate and, if needed, after all delegate methods have been invoked.
[BTW, while a bit of good humor in comments and diagnostic messages is always welcome, it's rather unprofessional to use "motherf..." in any code that you might expose to others.]
Well, I still don't know what happened.. but it seems that I have found a work around.
In mu Channel.m
file I changed the content slightly. Instead of using the NetworkQueue
I am now just doing a startAsynchronous
on the request object.
The end result:
[...]
if (fileExists)
{
NSLog(@"Downloaded image!");
[[self delegate] performSelector:[self didRecieveImageForChannelSelector] withObject:imagePath withObject:indexPath];
}
else {
NSLog(@"Need to fetch the image!");
NSURL *theUrl = [NSURL URLWithString:url];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:theUrl];
[request setDidFinishSelector:@selector(channelImageFetched:)];
[request setDidFailSelector:@selector(processFailed:)];
[request setTimeOutSeconds:60];
[request setDownloadDestinationPath:imagePath];
objc_setAssociatedObject(request, &kAssociationKey, indexPath, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[request startAsynchronous];
}
[...]
精彩评论