diff --git a/MKNetworkKit/MKNetworkEngine.h b/MKNetworkKit/MKNetworkEngine.h index 8e64de7..6d54180 100644 --- a/MKNetworkKit/MKNetworkEngine.h +++ b/MKNetworkKit/MKNetworkEngine.h @@ -138,7 +138,7 @@ params:(NSMutableDictionary*) body; /*! - * @abstract Creates a simple GET Operation with a request URL, parameters and HTTP Method + * @abstract Creates a simple Operation with a request URL, parameters and HTTP Method * * @discussion * Creates an operation with the given absolute URL. @@ -146,11 +146,27 @@ * The default headers you specified in your MKNetworkEngine subclass gets added to the headers * The params dictionary in this method gets attached to the URL as query parameters if the HTTP Method is GET/DELETE * The params dictionary is attached to the body if the HTTP Method is POST/PUT + * This method can be over-ridden by subclasses to tweak the operation creation mechanism. + * You would typically over-ride this method to create a subclass of MKNetworkOperation (if you have one). After you create it, you should call [super prepareHeaders:operation] to attach any custom headers from super class. + * @seealso + * prepareHeaders: */ -(MKNetworkOperation*) operationWithURLString:(NSString*) urlString params:(NSMutableDictionary*) body httpMethod:(NSString*) method; +/*! + * @abstract adds the custom default headers + * + * @discussion + * This method adds custom default headers to the factory created MKNetworkOperation. + * This method can be over-ridden by subclasses to add more default headers if necessary. + * You would typically over-ride this method if you have over-ridden operationWithURLString:params:httpMethod:. + * @seealso + * operationWithURLString:params:httpMethod: + */ + +-(void) prepareHeaders:(MKNetworkOperation*) operation; /*! * @abstract Handy helper method for fetching images * @@ -164,10 +180,24 @@ * @abstract Enqueues your operation into the shared queue * * @discussion - * The operation you created is enqueued to the shared queue + * The operation you created is enqueued to the shared queue. If the response for this operation was previously cached, the cached data will be returned. + * @seealso + * enqueueOperation:forceReload: */ -(void) enqueueOperation:(MKNetworkOperation*) request; +/*! + * @abstract Enqueues your operation into the shared queue. + * + * @discussion + * The operation you created is enqueued to the shared queue. + * When forceReload is NO, this method behaves like enqueueOperation: + * When forceReload is YES, No cached data will be returned even if cached data is available. + * @seealso + * enqueueOperation: + */ +-(void) enqueueOperation:(MKNetworkOperation*) operation forceReload:(BOOL) forceReload; + /*! * @abstract HostName of the engine * @property readonlyHostName diff --git a/MKNetworkKit/MKNetworkEngine.m b/MKNetworkKit/MKNetworkEngine.m index cefdbbd..e261895 100644 --- a/MKNetworkKit/MKNetworkEngine.m +++ b/MKNetworkKit/MKNetworkEngine.m @@ -73,7 +73,7 @@ +(void) initialize { _sharedNetworkQueue = [[NSOperationQueue alloc] init]; [_sharedNetworkQueue addObserver:[self self] forKeyPath:@"operationCount" options:0 context:NULL]; [_sharedNetworkQueue setMaxConcurrentOperationCount:6]; - + }); } } @@ -92,9 +92,20 @@ - (id) initWithHostName:(NSString*) hostName customHeaderFields:(NSDictionary*) self.hostName = hostName; self.reachability = [Reachability reachabilityWithHostname:self.hostName]; [self.reachability startNotifier]; - + } - self.customHeaders = headers; + + if([headers objectForKey:@"User-Agent"] == nil) { + + NSMutableDictionary *newHeadersDict = [headers mutableCopy]; + NSString *userAgentString = [NSString stringWithFormat:@"%@/%@", + [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleIdentifierKey], + [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleVersionKey]]; + [newHeadersDict setObject:userAgentString forKey:@"User-Agent"]; + self.customHeaders = newHeadersDict; + } else { + self.customHeaders = headers; + } } return self; @@ -267,11 +278,17 @@ -(MKNetworkOperation*) operationWithURLString:(NSString*) urlString params:(NSMutableDictionary*) body httpMethod:(NSString*)method { - MKNetworkOperation *operation = [MKNetworkOperation operationWithURLString:urlString params:body httpMethod:method]; - [operation addHeaders:self.customHeaders]; + MKNetworkOperation *operation = [[MKNetworkOperation alloc] initWithURLString:urlString params:body httpMethod:method]; + + [self prepareHeaders:operation]; return operation; } +-(void) prepareHeaders:(MKNetworkOperation*) operation { + + [operation addHeaders:self.customHeaders]; +} + -(NSData*) cachedDataForOperation:(MKNetworkOperation*) operation { NSData *cachedData = [self.memoryCache objectForKey:[operation uniqueIdentifier]]; @@ -291,6 +308,11 @@ -(NSData*) cachedDataForOperation:(MKNetworkOperation*) operation { -(void) enqueueOperation:(MKNetworkOperation*) operation { + [self enqueueOperation:operation forceReload:NO]; +} + +-(void) enqueueOperation:(MKNetworkOperation*) operation forceReload:(BOOL) forceReload { + [operation setCacheHandler:^(MKNetworkOperation* completedCacheableOperation) { // if this is not called, the request would have been a non cacheable request @@ -302,30 +324,34 @@ -(void) enqueueOperation:(MKNetworkOperation*) operation { [self.cacheInvalidationParams setObject:completedCacheableOperation.cacheHeaders forKey:uniqueId]; }]; - NSData *cachedData = [self cachedDataForOperation:operation]; - double expiryTimeInSeconds = 0.0f; + double expiryTimeInSeconds = 0.0f; - if(cachedData) { - [operation setCachedData:cachedData]; + if(!forceReload) { + NSData *cachedData = [self cachedDataForOperation:operation]; + if(cachedData) { + [operation setCachedData:cachedData]; - NSString *uniqueId = [operation uniqueIdentifier]; - NSMutableDictionary *savedCacheHeaders = [self.cacheInvalidationParams objectForKey:uniqueId]; - // there is a cached version. - // this means, the current operation is a "GET" - if(savedCacheHeaders) { - NSString *expiresOn = [savedCacheHeaders objectForKey:@"Expires"]; - NSDate *expiresOnDate = [NSDate dateFromRFC1123:expiresOn]; - expiryTimeInSeconds = [expiresOnDate timeIntervalSinceNow]; - - [operation updateOperationBasedOnPreviousHeaders:savedCacheHeaders]; + NSString *uniqueId = [operation uniqueIdentifier]; + NSMutableDictionary *savedCacheHeaders = [self.cacheInvalidationParams objectForKey:uniqueId]; + // there is a cached version. + // this means, the current operation is a "GET" + if(savedCacheHeaders) { + NSString *expiresOn = [savedCacheHeaders objectForKey:@"Expires"]; + NSDate *expiresOnDate = [NSDate dateFromRFC1123:expiresOn]; + expiryTimeInSeconds = [expiresOnDate timeIntervalSinceNow]; + + [operation updateOperationBasedOnPreviousHeaders:savedCacheHeaders]; + } } } NSUInteger index = [_sharedNetworkQueue.operations indexOfObject:operation]; if(index == NSNotFound) { - + if(expiryTimeInSeconds <= 0) [_sharedNetworkQueue addOperation:operation]; + else if(forceReload) + [_sharedNetworkQueue addOperation:operation]; // else don't do anything } else { @@ -355,7 +381,7 @@ - (void)imageAtURL:(NSURL *)url onCompletion:(MKNKImageBlock) imageFetchedBlock } onError:^(NSError* error) { - + DLog(@"%@", error); }]; @@ -450,7 +476,7 @@ -(void) useCache { NSError *error = nil; [[NSFileManager defaultManager] createDirectoryAtPath:cacheDirectory withIntermediateDirectories:YES attributes:nil error:&error]; } - + NSString *cacheInvalidationPlistFilePath = [cacheDirectory stringByAppendingPathExtension:@"plist"]; BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:cacheInvalidationPlistFilePath]; @@ -459,7 +485,7 @@ -(void) useCache { { self.cacheInvalidationParams = [NSMutableDictionary dictionaryWithContentsOfFile:cacheInvalidationPlistFilePath]; } - + #if TARGET_OS_IPHONE [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveCache) name:UIApplicationDidReceiveMemoryWarningNotification @@ -470,14 +496,14 @@ -(void) useCache { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveCache) name:UIApplicationWillTerminateNotification object:nil]; - + #elif TARGET_OS_MAC - + #warning POSSIBLY INCOMPLETE FUNCTION (Subscribe to Mac related notification for serializing caches) - + #endif - + } @end diff --git a/MKNetworkKit/MKNetworkOperation.h b/MKNetworkKit/MKNetworkOperation.h index eee4908..055d916 100644 --- a/MKNetworkKit/MKNetworkOperation.h +++ b/MKNetworkKit/MKNetworkOperation.h @@ -57,18 +57,6 @@ typedef void (^MKNKErrorBlock)(NSError* error); BOOL _freezable; } -/*! - * @abstract Creates a simple network operation - * - * @discussion - * Creates an operation with the given URL string. - * The params dictionary in this method gets attached to the URL as query parameters if the HTTP Method is GET/DELETE - * The params dictionary is attached to the body if the HTTP Method is POST/PUT - */ -+ (id)operationWithURLString:(NSString *)urlString - params:(NSMutableDictionary *)params - httpMethod:(NSString *)method; - /*! * @abstract Request URL Property * @property url @@ -365,4 +353,7 @@ typedef void (^MKNKErrorBlock)(NSError* error); -(void) updateOperationBasedOnPreviousHeaders:(NSMutableDictionary*) headers; -(NSString*) uniqueIdentifier; +- (id)initWithURLString:(NSString *)aURLString + params:(NSMutableDictionary *)params + httpMethod:(NSString *)method; @end diff --git a/MKNetworkKit/MKNetworkOperation.m b/MKNetworkKit/MKNetworkOperation.m index cc62c2a..245588d 100644 --- a/MKNetworkKit/MKNetworkOperation.m +++ b/MKNetworkKit/MKNetworkOperation.m @@ -360,15 +360,6 @@ -(void) updateOperationBasedOnPreviousHeaders:(NSMutableDictionary*) headers { } } -+ (id)operationWithURLString:(NSString *)urlString - params:(NSMutableDictionary *)params - httpMethod:(NSString *)method -{ - return [[self alloc] initWithURLString:urlString - params:params - httpMethod:method]; -} - -(void) setUsername:(NSString*) username password:(NSString*) password { self.username = username; @@ -800,6 +791,7 @@ - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLRespon if([self.request.HTTPMethod isEqualToString:@"GET"]) { + // We have all this complicated cache handling since NSURLRequestReloadRevalidatingCacheData is not implemented // do cache processing only if the request is a "GET" method NSString *lastModified = [httpHeaders objectForKey:@"Last-Modified"]; NSString *eTag = [httpHeaders objectForKey:@"ETag"]; @@ -808,9 +800,6 @@ - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLRespon NSString *contentType = [httpHeaders objectForKey:@"Content-Type"]; // if contentType is image, - NSString *cacheControl = [httpHeaders objectForKey:@"Cache-Control"]; // max-age, must-revalidate, no-cache - NSArray *cacheControlEntities = [cacheControl componentsSeparatedByString:@","]; - NSDate *expiresOnDate = nil; if([contentType rangeOfString:@"image"].location != NSNotFound) { @@ -822,6 +811,9 @@ - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLRespon expiresOnDate = [[NSDate date] dateByAddingTimeInterval:kMKNetworkKitDefaultImageHeadRequestDuration]; } + NSString *cacheControl = [httpHeaders objectForKey:@"Cache-Control"]; // max-age, must-revalidate, no-cache + NSArray *cacheControlEntities = [cacheControl componentsSeparatedByString:@","]; + for(NSString *substring in cacheControlEntities) { if([substring rangeOfString:@"max-age"].location != NSNotFound) { diff --git a/README.mdown b/README.mdown index 533227c..f6d93cf 100644 --- a/README.mdown +++ b/README.mdown @@ -58,13 +58,12 @@ run this on the command line ###TODO * Method to freeze blocks (alternative techniques probably) -* UserAgent String (Don't advertise MKNetworkKit, this should be app name) * Multiple encoding types in URL -* Read about response redirects and implement delegate callbacks or use state variables -* Obey cache-expires HTTP headers +* Fix AppleDoc warnings * Example code for Mac * Cache strategy for Mac -* Fix AppleDoc warnings +* Scheduling in current runloop +* Caching NSURLConnection based on Keep-Alive I'll be working on this soon. But you can start forking it and getting it right