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 (Cocoa-dev@lists.apple.com)

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:
http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to arch...@mail-archive.com

Reply via email to