0

I am using a NSOperation queue to fetch images and shown them on my tableview cells. Until the image comes back I show the loading overlay and once the operation completes it intimates the delegate and then I remove the loading overlay.

Now, I wanted to time out the fetch operation after say 5 secs and remove the loading overlay but the timer based approach isn't working out. Please suggest.

Below is my code:

#import "MyImageFetchOperation.h"
#import "MyImageFetchController.h"
#import "MyHTTPRequest.h"

@interface MyImageFetchOperation ()

@property (nonatomic, strong) NSString *imageURL;
@property (nonatomic, weak) id <MyOperationCompletedDelegate> delegate;
@property (nonatomic, assign) BOOL isCompleted;
@property (nonatomic, weak) NSTimer *timeoutTimer;

@end

@implementation MyImageFetchOperation
@synthesize imageURL;
@synthesize delegate;
@synthesize isCompleted;

#define kMyImageFetchTimeout 5

#pragma mark -
#pragma mark Destruction

- (void)dealloc {
    self.delegate = nil;
}


- (id)initWithImageURL:(NSString *)iImageURL delegate:(id)iDelegate {
    if (self = [super init]) {
        self.imageURL = iImageURL;
        self.delegate = iDelegate;
    }
    return self;
}


- (void)main {
    self.isCompleted = NO;
    NSMutableURLRequest *aRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.imageURL]];
    [aRequest setTimeoutInterval:kMyImageFetchTimeout];
    [aRequest setHTTPMethod:@"GET"];

    self.timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:kMyImageFetchTimeout target:self selector:@selector(requestTimedOut) userInfo:nil repeats:NO];

    [NSURLConnection sendAsynchronousRequest:aRequest queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *iData, NSError *iConnectionError) {
        if (!self.isCompleted) {
            if (iConnectionError) {
                [[MyImageFetchController sharedRunnerImageFetchControllerMy] urlFailed:self.imageURL];
            }

            UIImage *anImage = [UIImage imageWithData:iData];

            if (anImage) {
                [MyUtilities cacheFile:anImage withName:[self.imageURL runnerMD5HashMy] toDirectory:[self.delegate cacheDirectoryForImages]];
            } else {
                [[MyImageFetchController sharedRunnerImageFetchControllerMy] urlFailed:self.imageURL];
            }

            [self.delegate operationCompletedForURL:self.imageURL];
            self.isCompleted = YES;
        }
    }];
}


- (void)requestTimedOut {
    self.isCompleted = YES;
    [self.timeoutTimer invalidate];
    self.timeoutTimer = nil;
    [[MyImageFetchController sharedRunnerImageFetchControllerMy] urlFailed:self.imageURL];
    [self.delegate operationCompletedForURL:self.imageURL];
}

@end
Abhinav
  • 37,684
  • 43
  • 191
  • 309
  • Why do you need the timer? You have set a timeout on the URL request, so the completion handler should be called after 5 seconds latest, with `iData == nil` if the request timed out. – Martin R Feb 06 '14 at 16:28
  • Martin, he does have a problem. From the NSMutableRequest.h comments on the setTimeoutInterval "Hence, when an instance of load activity occurs (e.g. bytes are received from the network for a request), the idle interval for a request is reset to 0" So if he sets the interval to 5 but the connection takes 4 chunks of data each of them taking 2 second to arrive, the connection may not even timeout. – David Carvalho Feb 06 '14 at 16:50

2 Answers2

0

Why don't you call

[self.delegate operationCompletedForURL:self.imageURL];

inside this if?

if (iConnectionError) {

As a side not, you shouldn't be using self inside that block, you should use a __block variable.

  • Well that is anyway getting called as this statement is not in any if or else. – Abhinav Feb 06 '14 at 16:26
  • Yeah, you're right. But then the delegate is being called correctly without the timer? The best option would be to use the timeout of the request (as you seem to be) and not use the timer. – David Carvalho Feb 06 '14 at 16:29
  • Yeah but the timout of the request does not guarentee to be actually timing out the request in that period. It works if server connection establishment is taking time but once server connection is made and server is little sluggish in responding back then it follows the default timeout. – Abhinav Feb 06 '14 at 16:38
  • Check this: http://stackoverflow.com/questions/8304702/how-do-i-create-a-nstimer-on-a-background-thread Steven Fisher says that for the NSTimer to fire the run loop of the thread it is created in needs to continue to exist, but your operation thread dies right after you send the NSURLConnection request. Try starting the timer in the main thread. – David Carvalho Feb 06 '14 at 16:54
0

You need a run loop to make your timer work, something like this:

- (void)main
{
    // your code
    yourTimer = ... // create your timer

    NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
    [runLoop addTimer:yourTimer forMode:NSRunLoopCommonModes];
    [runLoop run];
}

Oh, and, i guess it's better to set up your own autorelease pool in main.

MANIAK_dobrii
  • 6,014
  • 3
  • 28
  • 55