Skip to content

Instantly share code, notes, and snippets.

@xslim
Created October 8, 2012 22:41
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save xslim/3855430 to your computer and use it in GitHub Desktop.
Save xslim/3855430 to your computer and use it in GitHub Desktop.
AFIncrementalStore with If-Modified-Since hack
// Connecting AFIncrementalStore & MagicRecord
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
PDDebugger *debugger = [PDDebugger defaultInstance];
[debugger connectToURL:[NSURL URLWithString:@"ws://localhost:9000/device"]];
[debugger enableNetworkTrafficDebugging];
[debugger forwardAllNetworkTraffic];
[debugger enableCoreDataDebugging];
[self setupCoreDataStack];
// Bla boa
[self.window makeKeyAndVisible];
return YES;
}
- (void)setupCoreDataStack {
//[MagicalRecord setupCoreDataStackWithStoreNamed:@"Events.sqlite"];
NSManagedObjectModel *model = [NSManagedObjectModel MR_defaultManagedObjectModel];
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
AFIncrementalStore *incrementalStore = (AFIncrementalStore *)[psc addPersistentStoreWithType:[EventsIncrementalStore type] configuration:nil URL:nil options:nil error:nil];
NSDictionary *options = @{
NSInferMappingModelAutomaticallyOption : @(YES),
NSMigratePersistentStoresAutomaticallyOption: @(YES)
};
[incrementalStore.backingPersistentStoreCoordinator MR_addSqliteStoreNamed:@"Events.sqlite" withOptions:options];
[NSPersistentStore setDefaultPersistentStore:incrementalStore];
[NSPersistentStoreCoordinator MR_setDefaultStoreCoordinator:incrementalStore.persistentStoreCoordinator];
[NSManagedObjectContext MR_initializeDefaultContextWithCoordinator:incrementalStore.persistentStoreCoordinator];
// Totally needed to reset MR-MOCs after AFIS
[[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note) {
NSManagedObjectContext *moc0 = [note object];
NSManagedObjectContext *moc = [NSManagedObjectContext MR_rootSavingContext];
NSManagedObjectContext *moc2 = [NSManagedObjectContext MR_defaultContext];
if ((![moc0 isEqual:moc]) && (![moc0 isEqual:moc2]) && [moc0 persistentStoreCoordinator] == [moc persistentStoreCoordinator]) {
[moc reset];
[moc2 reset];
}
}];
}
//
// EventsAPIClient.m
//
// Created by Taras Kalapun on 10/8/12.
//
#import "EventsAPIClient.h"
static NSString * TKUnderscoresToCamelCaseString(NSString *underscores) {
NSMutableString *output = [NSMutableString string];
BOOL makeNextCharacterUpperCase = NO;
for (NSInteger idx = 0; idx < [underscores length]; idx += 1) {
unichar c = [underscores characterAtIndex:idx];
if (c == '_') {
makeNextCharacterUpperCase = YES;
} else if (makeNextCharacterUpperCase) {
[output appendString:[[NSString stringWithCharacters:&c length:1] uppercaseString]];
makeNextCharacterUpperCase = NO;
} else {
[output appendFormat:@"%C", c];
}
}
return output;
}
static NSString* const kUpdatedAt = @"updatedAt";
static NSString * const kAPIBaseURLString = @"http://events.dev/events/505735ec529fa42e1200000e/";
//static NSString * const kAPIBaseURLString = @"http://events.10.10.11.251.xip.io/events/505735ec529fa42e1200000e/";
@interface EventsAPIClient ()
@property (nonatomic, strong) NSManagedObjectContext *backingManagedObjectContext;
@property (nonatomic, strong) NSMutableDictionary *entityNetworkRequestPolling;
- (NSString *)lastModifiedForObject:(NSManagedObject *)object;
- (NSString *)lastModifiedForObjectsOfClass:(Class)objectClass;
@end
@implementation EventsAPIClient
@synthesize backingManagedObjectContext=_backingManagedObjectContext;
+ (EventsAPIClient *)sharedClient {
static EventsAPIClient *_sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedClient = [[self alloc] initWithBaseURL:[NSURL URLWithString:kAPIBaseURLString]];
});
return _sharedClient;
}
- (id)initWithBaseURL:(NSURL *)url {
self = [super initWithBaseURL:url];
if (!self) {
return nil;
}
// 304 Not-Modified
[AFJSONRequestOperation addAcceptableStatusCodes:[NSIndexSet indexSetWithIndex:304]];
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
[self setDefaultHeader:@"Accept" value:@"application/json"];
[self setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
self.operationQueue.suspended = (status == AFNetworkReachabilityStatusNotReachable);
}];
self.supportRelationsByID = YES;
self.entityNetworkRequestPolling = [NSMutableDictionary dictionary];
return self;
}
- (NSString *)pathForEntity:(NSEntityDescription *)entity {
// Remove Prefix 'E'
NSString *name = [entity.name substringFromIndex:1];
return AFPluralizedString(name);
}
- (NSDictionary *)attributesForRepresentation:(NSDictionary *)representation
ofEntity:(NSEntityDescription *)entity
fromResponse:(NSHTTPURLResponse *)response
{
NSDictionary *keyMapping = nil;
Class entityClass = NSClassFromString(entity.name);
if ([entityClass respondsToSelector:@selector(mapping)]) {
keyMapping = [entityClass performSelector:@selector(mapping)];
}
NSMutableDictionary *newRepresentation = [NSMutableDictionary dictionaryWithCapacity:representation.count];
for (NSString *key in representation) {
NSString *newKey = nil;
if (keyMapping && keyMapping[key]) {
newKey = keyMapping[key];
} else {
newKey = TKUnderscoresToCamelCaseString(key);
}
newRepresentation[newKey] = representation[key];
}
NSMutableDictionary *mutablePropertyValues = [[super attributesForRepresentation:newRepresentation ofEntity:entity fromResponse:response] mutableCopy];
if ([entity.name isEqualToString:@"EAct"]) {
//NSString *description = [representation valueForKey:@"description"];
//[mutablePropertyValues setValue:description forKey:@"artistDescription"];
}
return mutablePropertyValues;
}
- (BOOL)shouldFetchRemoteAttributeValuesForObjectWithID:(NSManagedObjectID *)objectID inManagedObjectContext:(NSManagedObjectContext *)context {
return NO;
//return [[[objectID entity] name] isEqualToString:@"Artist"];
}
- (BOOL)shouldFetchRemoteValuesForRelationship:(NSRelationshipDescription *)relationship forObjectWithID:(NSManagedObjectID *)objectID inManagedObjectContext:(NSManagedObjectContext *)context {
return NO;
//return [[[objectID entity] name] isEqualToString:@"Artist"];
}
- (BOOL)shouldMakeNetworkRequestForFetchRequest:(NSFetchRequest *)fetchRequest
{
static const NSTimeInterval kRelaxTime = 300; //5*60
NSString *requestName = fetchRequest.entityName;
NSDate *previousRequestDate = self.entityNetworkRequestPolling[requestName];
NSDate *currentDate = [NSDate date];
if (previousRequestDate && [previousRequestDate timeIntervalSinceDate:currentDate] < kRelaxTime) {
NSLog(@"Polling: %@ - %@", requestName, previousRequestDate);
return NO;
//TODO: Would be nice here to make Backed request
}
self.entityNetworkRequestPolling[requestName] = currentDate;
NSLog(@"Requesting: %@ - %@", requestName, currentDate);
return YES;
}
- (NSMutableURLRequest *)requestForFetchRequest:(NSFetchRequest *)fetchRequest
withContext:(NSManagedObjectContext *)context
{
//Due Backing hack, not realy needed, but just in case
if (fetchRequest.propertiesToFetch.count == 1 && [[[fetchRequest.propertiesToFetch objectAtIndex:0] name] isEqualToString:kUpdatedAt]) {
return nil;
}
if (![self shouldMakeNetworkRequestForFetchRequest:fetchRequest]) return nil;
static NSString * const kModifiedSinceHeader = @"If-Modified-Since";
NSMutableURLRequest *request = [super requestForFetchRequest:fetchRequest withContext:context];
//This is for testing real 304 responses
// TODO: Check if needed
request.cachePolicy = NSURLRequestReloadIgnoringCacheData;
NSString *lastModified = [self lastModifiedForObjectsOfClass:NSClassFromString(fetchRequest.entity.name)];
if (lastModified) [request setValue:lastModified forHTTPHeaderField:kModifiedSinceHeader];
//return nil;
return request;
}
#pragma mark - Getting Last-Updated-At
- (NSString *)lastModifiedFromDate:(NSDate *)date {
// RFC1123
static NSDateFormatter *timeDateFormatter = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
timeDateFormatter = [[NSDateFormatter alloc] init];
timeDateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
timeDateFormatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
timeDateFormatter.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'";
});
return [timeDateFormatter stringFromDate:date];
}
- (NSDate *)lastUpdatedAtForObjectsOfClass:(Class)objectClass {
// TODO: Think if better to store in User-Info
NSString *entityName = NSStringFromClass(objectClass);
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entityName];
[request setFetchBatchSize:1];
[request setFetchLimit:1];
[request setSortDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:kUpdatedAt ascending:NO]]];
[request setPropertiesToFetch:@[kUpdatedAt]];
NSManagedObjectContext *context = self.backingManagedObjectContext;
NSArray *objects = [context executeFetchRequest:request error:NULL];
NSManagedObject *obj = (objects.count > 0) ? objects[0] : nil;
//NSManagedObject *obj = [objectClass MR_executeFetchRequestAndReturnFirstObject:request inContext:[NSManagedObjectContext MR_contextForCurrentThread]];
return [obj valueForKey:kUpdatedAt];
}
- (NSString *)lastModifiedForObjectsOfClass:(Class)objectClass {
return [self lastModifiedFromDate:[self lastUpdatedAtForObjectsOfClass:objectClass]];
}
- (NSString *)lastModifiedForObject:(NSManagedObject *)object {
return [self lastModifiedFromDate:[object valueForKey:kUpdatedAt]];
}
#pragma mark - Backing context hack
- (NSManagedObjectContext *)backingManagedObjectContext {
if (!_backingManagedObjectContext) {
_backingManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
AFIncrementalStore *store = (id)[NSPersistentStore MR_defaultPersistentStore];
_backingManagedObjectContext.persistentStoreCoordinator = store.backingPersistentStoreCoordinator;
//_backingManagedObjectContext.persistentStoreCoordinator = [NSPersistentStoreCoordinator MR_defaultStoreCoordinator];
//_backingManagedObjectContext.retainsRegisteredObjects = YES;
}
return _backingManagedObjectContext;
}
@end
platform :ios, '5.0'
# Core
pod 'AFIncrementalStore', :git => 'https://github.com/xslim/AFIncrementalStore' #, :head
pod 'MagicalRecord', :podspec => 'https://raw.github.com/magicalpanda/MagicalRecord/master/MagicalRecord.podspec'
# Helpers
pod 'ISO8601DateFormatter'
#pod 'AFHTTPRequestOperationLogger'
pod 'PonyDebugger', :head
# UI
pod 'TKThemeManager', :podspec => 'https://raw.github.com/xslim/TKThemeManager/master/TKThemeManager.podspec'
pod 'UIGlossyButton'
pod 'SVProgressHUD'
pod 'DDPageControl', '0.1'
pod 'JMTabView'
pod 'MHTabBarController', :podspec => 'https://raw.github.com/gist/3832321/14c550aa22b2d784f2e9b1fc06eea323ec8b6e26/MHTabBarController.podspec'
# Social
pod 'Facebook-iOS-SDK', '1.2'
pod 'ShareKit/Facebook'
pod 'ShareKit/Twitter'
pod 'FlurrySDK'
# Testing
pod 'TestFlightSDK'
pod 'DCIntrospect'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment