ALHTTPRequest

From macwrench
Jump to navigation Jump to search

Spätestens seitdem einige der weitverbreitetsten Bibliotheken für HTTP-Requests unter iOS – wie beispielsweise ASIHTTPRequest – nicht mehr aktiv weiterentwickelt werden (oder zumindest nur mit einigem Aufwand in ARC-Projekten nutzbar sind), stieg bei mir der Bedarf nach einer ganz einfachen Klasse, die nichts Anderes als ein simpler Wrapper für NSURLConnection ist und einfach nur asynchron den Inhalt einer URL herunterlädt, ohne den MainThread zu blockieren.

  Logo informations.svg
Diese Klasse wurde für die Verwendung in iOS-Projekten erstellt, die ARC (Automatic Reference Counting) verwenden und setzt damit mindestens iOS 4.0 als deployment target voraus.
Bevor ein Aufruf abgesetzt wird, sollte mittels Reachability geprüft werden, ob überhaupt eine Internet-Verbindung besteht.
 

Verwendung

Die Klasse "ALHTTPRequest" definiert ein Protokoll namens "ALHTTPRequestDelegate", das in der jeweiligen Klasse einzubinden ist:

@interface MyClass <ALHTTPRequestDelegate> {
    ...
}
...
@end

Die verfügbaren Delegate-Methoden sind allesamt optional:

- (void)requestDidFinish:(ALHTTPRequest*)request;
- (void)requestDidFail:(ALHTTPRequest*)request;
- (void)requestDidFailWithError:(NSError*)error;
- (void)requestDidStart;

Das Absetzen eines HTTP Requests erfolgt dann beispielsweise so:

Reachability *reachable = [Reachability reachabilityForInternetConnection];
if ([reachable currentReachabilityStatus] != NotReachable) {
    NSURL *url = [NSURL URLWithString:@"http://www.example.com"];
    ALHTTPRequest *request = [ALHTTPRequest requestWithURL:url delegate:self];
    [request start];
}

Die Reachability-Klasse steht hier zum Download zur Verfügung.

Der Inhalt der Antwort aus requestDidFinish: lässt sich beispielsweise so extrahieren:

- (void)requestDidFinish:(NSData*)responseData {
    if (responseData != nil) {
        NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSASCIIStringEncoding];
        NSLog(@"Antwort: %@",responseString);
    }
}

Code

ALHTTPRequest.h

//
//  ALHTTPRequest.h
//
//  Created by Alexander Lindenstruth on 13.04.12.
//  Copyright (c) 2012 Alexander Lindenstruth. All rights reserved.
//
//  Version 1.1

#import <Foundation/Foundation.h>

typedef enum{
	ALHTTPRequestErrorUnknown = 0,
	ALHTTPRequestErrorInvalidURL,
	ALHTTPRequestErrorNoResponse,
	ALHTTPRequestErrorCancelled,
	ALHTTPRequestErrorIsExecuting
} ALHTTPRequestError;

@protocol ALHTTPRequestDelegate;

@interface ALHTTPRequest : NSObject <NSURLConnectionDelegate> {
	NSURLConnection *_urlConnection;
	NSMutableData *_loadedData;
}

@property (nonatomic, strong) NSURL *url;
@property (nonatomic, assign) id<ALHTTPRequestDelegate> delegate;
@property NSInteger status;

+ (ALHTTPRequest*)requestWithURL:(NSURL*)url delegate:(id)delegate;
- (void)start;
- (void)cancel;
- (NSData*)responseData;
- (NSString*)responseString;
- (NSDictionary*)responseDictionary;

@end

@protocol ALHTTPRequestDelegate<NSObject>
@optional
- (void)requestDidStart;
- (void)requestDidFinish:(ALHTTPRequest*)request;
- (void)requestDidFail:(ALHTTPRequest*)request;
- (void)requestDidFailWithError:(NSError*)error;
@end

ALHTTPRequest.m

//
//  ALHTTPRequest.m
//
//  Created by Alexander Lindenstruth on 13.04.12.
//  Copyright (c) 2012 Alexander Lindenstruth. All rights reserved.
//  
//  Version 1.1

#import "ALHTTPRequest.h"

@interface ALHTTPRequest(Private)
- (void)_fail;
- (void)_finish;
- (NSInteger)_minimumLengthOfUrl;
- (NSURL*)_alteredUrl;
@end

@implementation ALHTTPRequest

@synthesize delegate, url, status;

+ (ALHTTPRequest*)requestWithURL:(NSURL*)url delegate:(id)delegate {
	ALHTTPRequest *request = [[ALHTTPRequest alloc] init];
	request.url = url;
	request.delegate = delegate;
	return request;
}

- (void)start {
	if (_urlConnection != nil) {
		[self _fail];
		return;
	}
	if (![self.url isKindOfClass:[NSURL class]]) {
		[self _fail];
		return;
	}
	if ([[self.url absoluteString] length] < [self _minimumLengthOfUrl]) {
		[self _fail];
	}
	NSURLRequest *request = nil;
	NSURL *processedUrl = [self _alteredUrl];
	if ([processedUrl isKindOfClass:[NSURL class]]) {
		request = [NSURLRequest requestWithURL:processedUrl];
	} else {
		request = [NSURLRequest requestWithURL:self.url];
	}
	if (request == nil) {
		[self _fail];
		return;
	}
	_loadedData = nil;
	self.status = 0;
	_urlConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
	if (_urlConnection == nil) {
		[self _fail];
		return;
	} else {
		if ([self delegate] != nil) {
			if ([[self delegate] respondsToSelector:@selector(requestDidStart)]) {
				[[self delegate] performSelector:@selector(requestDidStart)];
			}
		}
	}
}

- (void)cancel {
	[_urlConnection cancel];
	_urlConnection = nil;
	if ([self delegate] != nil) {
		if ([[self delegate] respondsToSelector:@selector(requestDidFail:)]) {
			[[self delegate] performSelector:@selector(requestDidFail:) withObject:self];
		}
		if ([[self delegate] respondsToSelector:@selector(requestDidFailWithError:)]) {
			[[self delegate] performSelector:@selector(requestDidFailWithError:) withObject:[NSError errorWithDomain:@"ALHTTPRequestError" code:ALHTTPRequestErrorCancelled userInfo:nil]];
		}
	}
}

- (void)reset {
	[_urlConnection cancel];
	_urlConnection = nil;
	_loadedData = nil;
	self.status = 0;
}

- (NSData*)responseData {
	if (_loadedData != nil) {
		return [NSData dataWithData:_loadedData];
	}
	return nil;
}

- (NSString*)responseString {
	if ([self responseData] != nil) {
		return [[NSString alloc] initWithData:[self responseData]  encoding:NSASCIIStringEncoding];;
	}
	return nil;
}

- (NSDictionary*)responseDictionary {
	if ([self responseData] != nil) {
		NSDictionary *dict = nil;
		@try {
			NSPropertyListFormat format;
			NSError *error = nil;
			dict = [NSPropertyListSerialization propertyListWithData:[self responseData] options:NSPropertyListMutableContainers format:&format error:&error];
			if (error == nil) {
				return dict;
			}
		}
		@catch (NSException *exception) {
			NSLog(@"ERROR: invalid feed loaded from %@",[self url]);
		}
		@finally {
			
		}
	}
	return nil;
}

#pragma mark -
#pragma mark NSURLConnectionDelegate

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    _loadedData = nil;
    _loadedData = [[NSMutableData alloc] init];
	
    NSHTTPURLResponse * httpResponse = (NSHTTPURLResponse *)response;
	self.status = [httpResponse statusCode];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [_loadedData appendData:data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
	if ([self delegate] != nil) {
		if ([[self delegate] respondsToSelector:@selector(requestDidFinish:)]) {
			[[self delegate] performSelector:@selector(requestDidFinish:) withObject:self];
		}
	}
	_urlConnection = nil;
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
	_urlConnection = nil;
	_loadedData = nil;
	self.status = 0;
	if ([self delegate] != nil) {
		if ([[self delegate] respondsToSelector:@selector(requestDidFail:)]) {
			[[self delegate] performSelector:@selector(requestDidFail:) withObject:self];
		}
		if ([[self delegate] respondsToSelector:@selector(requestDidFailWithError:)]) {
			[[self delegate] performSelector:@selector(requestDidFailWithError:) withObject:error];
		}
	}
}

#pragma mark -
#pragma mark private methods

- (NSURL*) _alteredUrl {
	// use this method to alter urls dynamically when subclassing ALHTTPRequest
	return nil;
}

- (NSInteger)_minimumLengthOfUrl {
	return 10;
}

- (void)_finish {
	
}

- (void)_fail {
	if ([self delegate] != nil) {
		if ([[self delegate] respondsToSelector:@selector(requestDidFail:)]) {
			[[self delegate] performSelector:@selector(requestDidFail:) withObject:self];
		}
		if ([[self delegate] respondsToSelector:@selector(requestDidFailWithError:)]) {
			ALHTTPRequestError errorCode = ALHTTPRequestErrorUnknown;
			if (![self.url isKindOfClass:[NSURL class]] || [[self.url absoluteString] length] < [self _minimumLengthOfUrl]) {
				errorCode = ALHTTPRequestErrorInvalidURL;
			}
			if (_urlConnection != nil) {
				errorCode = ALHTTPRequestErrorIsExecuting;
			}
			[[self delegate] performSelector:@selector(requestDidFailWithError:) withObject:[NSError errorWithDomain:@"ALHTTPRequestError" code:errorCode userInfo:nil]];
		}
	}
}
@end

Changelog

Version 1.1
  • Delegate-Methode requestDidFinish: Parameter geändert
  • Delegate-Methode requestDidFail: Parameter geändert
  • Delegate-Methode requestDidFailWithError: hinzugefügt
  • Getter responseString und responseDictionary hinzugefügt
  • Methode reset hinzugefügt