Created
October 8, 2012 22:41
-
-
Save xslim/3855430 to your computer and use it in GitHub Desktop.
AFIncrementalStore with If-Modified-Since hack
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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]; | |
} | |
}]; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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