Re: Seeking advice for how to implement "notification" upon completion of asynchronous upload
Re: Seeking advice for how to implement "notification" upon completion of asynchronous upload
- Subject: Re: Seeking advice for how to implement "notification" upon completion of asynchronous upload
- From: WT <email@hidden>
- Date: Fri, 01 Apr 2011 02:35:50 -0300
On Mar 31, 2011, at 9:08 PM, Chris Markle wrote:
> Still fairly new here to iOS and Objective-C programming, so looking
> for some advice to help keep me from going down the wrong road(s)... I
> would like to build an Objective-C class or classes to perform a file
> upload using a non-standard protocol. It would run the upload
> asynchronously and would need to notify the caller upon completion. I
> see a variety of techniques used or discussed for this and I was
> hoping to get some advice on which pattern or patterns I should use
> and which I should stay away from. I have no issue with using stuff
> that only runs on the latest iOS or OS X systems, so supporting older
> OS's in not a concern for me.
>
> I see in particular:
>
> 1. delegates - e.g., NSURLConnection connection:didFailWithError: and
> connectionDidFinishLoading:, or similar ones in ASIHTTPRequest
> requestFinished or requestFailed
>
> 2. blocks - e.g., ASIURLRequest completionBlock or failedBlock, or
> ALAssetsLibrary assetForURL:resultBlock:failureBlock: (ASIHTTPRequest
> actually implements delegates and blocks and allows you to mix and
> match them.)
>
> 3. KVO (key-value observation) - e.g., some service where you observe
> an isFinished property
>
> 4. Notifications (NSNotificationCenter, etc.) - haven't really seen
> this used much but a lot of people seem to talk about it as a good
> approach
>
> There are probably other techniques as well...
>
> For a new, modern API which approach or approaches should I use or not
> use? I suppose my main interest here is to use something that's
> flexible to the programmer that uses my API, and something that is
> conventional ion its approach so that the programmer is using
> something normal and not unusual.
>
> Thanks in advance for any counsel on this...
>
> Chris
Hi Chris,
I've written a fairly general Downloader class that takes in a url and some identifying token (so you can recognize the Downloader instance later on) and asynchronously downloads the url's content. It invokes methods on its delegate when it's done and on fail, passing it the data if the download succeeded. You can create multiple instances and they'll work independently. It also automatically keeps track of maintaining state for the network activity indicator (and does so in a thread-safe manner, even with many Downloader instances doing their thing).
It works great, for instance, when you want to populate the rows of a table view with some downloaded data (eg, images from a web service), in which case you'd use the row's index path as the identifying token. You can then fire as many Downloader instances as there are visible rows and update each row as its downloader is done.
Hope this helps.
WT
// Downloader.h
// Copyright 2010 Wagner Truppel. All rights reserved.
// Use/change as you see fit, but at your own risk.
// Usage:
// Downloader* downloader = [[Downloader alloc] init];
// downloader.idToken = ...
// downloader.urlStr = ...
// downloader.delegate = ...
// [downloader startDownload];
// If there's a need to abort the download, invoke -stopDownload on
// the instance in question.
// Note: the public methods are NOT thread safe.
#import <Foundation/Foundation.h>
@protocol DownloaderDelegate;
// ========================================================================= //
@interface Downloader: NSObject
{
@private
id <DownloaderDelegate> delegate_;
id idToken_;
NSString* urlStr_;
NSURLConnection* connection_;
NSMutableData* activeData_;
BOOL isDownloading_;
}
// ======================================== //
@property (readwrite, nonatomic, assign) id <DownloaderDelegate> delegate;
@property (readwrite, nonatomic, retain) id idToken;
@property (readwrite, nonatomic, retain) NSString* urlStr;
// ======================================== //
- (void) startDownload;
- (void) stopDownload;
@end
// ========================================================================= //
@protocol DownloaderDelegate <NSObject>
// NOTE: these are invoked and executed in the main thread.
- (void) downloader: (Downloader*) downloader
didFinishDownloadingData: (NSData*) data;
- (void) downloader: (Downloader*) downloader
failedWithError: (NSError*) error;
@end
// ========================================================================= //
// Downloader.m
// Copyright 2010 Wagner Truppel. All rights reserved.
// Use/change as you see fit, but at your own risk.
#import "Downloader.h"
// ========================================================================= //
// Keeps track of the number of active connections so we can keep the
// network activity indicator on when there are active connections.
static NSInteger stConnectionCount = 0;
// ========================================================================= //
@interface Downloader ()
@property (readwrite, nonatomic, retain) NSURLConnection* connection;
@property (readwrite, nonatomic, retain) NSMutableData* activeData;
@end
// ========================================================================= //
@interface Downloader (Private)
- (void) incrementActivityCount;
- (void) decrementActivityCount;
- (void) didFinishDownloadingData;
- (void) failedWithError: (NSError*) error;
- (void) cleanup;
@end
// ========================================================================= //
@implementation Downloader
@synthesize delegate = delegate_;
@synthesize idToken = idToken_;
@synthesize urlStr = urlStr_;
@synthesize connection = connection_;
@synthesize activeData = activeData_;
// ======================================== //
- (void) dealloc;
{
[self cleanup];
self.delegate = nil;
self.idToken = nil;
self.urlStr = nil;
[super dealloc];
}
// ======================================== //
- (void) startDownload;
{
if (isDownloading_)
{ return; }
isDownloading_ = YES;
NSLog(@"starting download from:");
NSLog(@"%@", self.urlStr);
NSLog(@"");
[self performSelectorOnMainThread: @selector(incrementActivityCount)
withObject: nil waitUntilDone: NO];
self.activeData = [NSMutableData data];
NSURLRequest* urlRequest = [NSURLRequest
requestWithURL: [NSURL URLWithString: self.urlStr]
cachePolicy: NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval: 20];
// Allocated here but released on completion or failure
// (through the -cleanup method).
NSURLConnection* connection = [[NSURLConnection alloc]
initWithRequest: urlRequest delegate: self];
self.connection = connection;
[connection release];
}
// ======================================== //
- (void) stopDownload;
{
if (! isDownloading_)
{ return; }
[self cleanup];
}
// ========================================================================= //
#pragma mark NSURLConnection delegate methods
// ========================================================================= //
- (void) connection: (NSURLConnection*) connection
didReceiveData: (NSData*) data;
{
[self.activeData appendData: data];
}
// ======================================== //
- (void) connectionDidFinishLoading: (NSURLConnection*) connection;
{
[self performSelectorOnMainThread: @selector(didFinishDownloadingData)
withObject: nil waitUntilDone: NO];
}
// ======================================== //
- (void) connection: (NSURLConnection*) connection
didFailWithError: (NSError*) error;
{
[self performSelectorOnMainThread: @selector(failedWithError:)
withObject: error waitUntilDone: NO];
}
// ========================================================================= //
#pragma mark private methods
// ========================================================================= //
- (void) incrementActivityCount;
{
stConnectionCount += 1;
NSLog(@"stConnectionCount: %i", stConnectionCount);
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}
// ======================================== //
- (void) decrementActivityCount;
{
stConnectionCount -= 1;
NSLog(@"stConnectionCount: %i", stConnectionCount);
if (stConnectionCount == 0)
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
}
// ======================================== //
- (void) didFinishDownloadingData;
{
if ([self.delegate respondsToSelector:
@selector(downloader: didFinishDownloadingData:)])
{
[self.delegate downloader: self
didFinishDownloadingData: self.activeData];
}
[self cleanup];
}
// ======================================== //
- (void) failedWithError: (NSError*) error;
{
if ([self.delegate respondsToSelector:
@selector(downloader: failedWithError:)])
{
[self.delegate downloader: self
failedWithError: error];
}
[self cleanup];
}
// ======================================== //
- (void) cleanup;
{
if (self.connection != nil)
{
[self.connection cancel];
[self performSelectorOnMainThread: @selector(decrementActivityCount)
withObject: nil waitUntilDone: NO];
}
self.connection = nil;
self.activeData = nil;
isDownloading_ = NO;
}
// ========================================================================= //
@end
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden