diff --git a/Classes/Private/Managers/APTaskManager.h b/Classes/Private/Managers/APTaskManager.h index 920488b..dd6b643 100644 --- a/Classes/Private/Managers/APTaskManager.h +++ b/Classes/Private/Managers/APTaskManager.h @@ -11,8 +11,8 @@ @interface APTaskManager : NSObject -- (void)addTaskWithURL:(NSURL *)url block:(APTaskCallbackBlock)block - shouldStart:(BOOL *)shouldStart; +- (APStorageTask *)addTaskWithURL:(NSURL *)url storeInMemory:(BOOL)storeInMemory + callbackBlock:(APTaskCallbackBlock)callbackBlock; - (void)finishTaskWithURL:(NSURL *)url object:(id)object error:(NSError *)error; - (void)cancelTaskWithURL:(NSURL *)url; - (void)cancelAllTasks; diff --git a/Classes/Private/Managers/APTaskManager.m b/Classes/Private/Managers/APTaskManager.m index 2f5c40d..f6cbaf4 100644 --- a/Classes/Private/Managers/APTaskManager.m +++ b/Classes/Private/Managers/APTaskManager.m @@ -32,25 +32,26 @@ - (id)init #pragma mark - public -- (void)addTaskWithURL:(NSURL *)url block:(APTaskCallbackBlock)block shouldStart:(BOOL *)shouldStart +- (APStorageTask *)addTaskWithURL:(NSURL *)url storeInMemory:(BOOL)storeInMemory + callbackBlock:(APTaskCallbackBlock)callbackBlock { + APStorageTask *task; NSString *key = url.absoluteString; if (key) { - BOOL isShouldStartTask = NO; - APStorageTask *task = [_dictionary objectForKeySynchronously:key]; + task = [_dictionary objectForKeySynchronously:key]; if (!task) { - task = [[APStorageTask alloc] init]; + task = [[APStorageTask alloc] initWithTaskURL:url]; [_dictionary setObject:task forKey:key]; - isShouldStartTask = YES; } - [task addCallbackBlock:block thread:NSThread.currentThread]; - if (shouldStart) + if (storeInMemory) { - *shouldStart = isShouldStartTask; + task.storeInMemory = storeInMemory; } + [task addCallbackBlock:callbackBlock thread:NSThread.currentThread]; } + return task; } - (void)finishTaskWithURL:(NSURL *)url object:(id)object error:(NSError *)error diff --git a/Classes/Private/Models/APStorageTask.h b/Classes/Private/Models/APStorageTask.h index 82bf106..9211977 100644 --- a/Classes/Private/Models/APStorageTask.h +++ b/Classes/Private/Models/APStorageTask.h @@ -12,6 +12,11 @@ typedef void (^APTaskCallbackBlock)(id object, NSError *error); @interface APStorageTask : NSObject +@property (nonatomic, readonly) NSURL *url; +@property (nonatomic, readonly) BOOL isShouldRun; +@property (atomic, assign) BOOL storeInMemory; + +- (id)initWithTaskURL:(NSURL *)url; - (void)addCallbackBlock:(APTaskCallbackBlock)block thread:(NSThread *)thread; - (void)performCallbackWithObject:(id)object error:(NSError *)error; diff --git a/Classes/Private/Models/APStorageTask.m b/Classes/Private/Models/APStorageTask.m index fe8c965..dd28ecb 100644 --- a/Classes/Private/Models/APStorageTask.m +++ b/Classes/Private/Models/APStorageTask.m @@ -10,11 +10,24 @@ #import "NSThread+Block.h" @interface APStorageTask () -@property (nonatomic, copy) APTaskCallbackBlock callbackBlock; +@property (atomic, copy) APTaskCallbackBlock callbackBlock; @end @implementation APStorageTask +#pragma mark - life cycle + +- (id)initWithTaskURL:(NSURL *)url +{ + self = [super init]; + if (self) + { + _url = url; + _isShouldRun = YES; + } + return self; +} + #pragma mark - public - (void)addCallbackBlock:(APTaskCallbackBlock)block thread:(NSThread *)thread @@ -36,6 +49,7 @@ - (void)addCallbackBlock:(APTaskCallbackBlock)block thread:(NSThread *)thread previousBlock(object, error); threadBlock(object, error); }; + _isShouldRun = NO; } } } diff --git a/Classes/Public/APSmartStorage.h b/Classes/Public/APSmartStorage.h index a7c2025..0633de0 100644 --- a/Classes/Public/APSmartStorage.h +++ b/Classes/Public/APSmartStorage.h @@ -18,7 +18,11 @@ typedef id (^APParsingBlock)(NSData *data, NSURL *url); + (instancetype)sharedInstance; - (void)loadObjectWithURL:(NSURL *)url callback:(void (^)(id object, NSError *))callback; +- (void)loadObjectWithURL:(NSURL *)url storeInMemory:(BOOL)storeInMemory + callback:(void (^)(id object, NSError *))callback; - (void)reloadObjectWithURL:(NSURL *)url callback:(void (^)(id object, NSError *))callback; +- (void)reloadObjectWithURL:(NSURL *)url storeInMemory:(BOOL)storeInMemory + callback:(void (^)(id object, NSError *))callback; - (void)removeObjectWithURLFromMemory:(NSURL *)url; - (void)removeObjectWithURLFromStorage:(NSURL *)url; - (void)removeAllFromMemory; diff --git a/Classes/Public/APSmartStorage.m b/Classes/Public/APSmartStorage.m index 1cabc29..b7e03fa 100644 --- a/Classes/Public/APSmartStorage.m +++ b/Classes/Public/APSmartStorage.m @@ -65,12 +65,18 @@ + (instancetype)sharedInstance - (void)loadObjectWithURL:(NSURL *)url callback:(void (^)(id object, NSError *))callback { - BOOL isShouldStart = NO; - [self.taskManager addTaskWithURL:url block:callback shouldStart:&isShouldStart]; - if (isShouldStart) + [self loadObjectWithURL:url storeInMemory:YES callback:callback]; +} + +- (void)loadObjectWithURL:(NSURL *)url storeInMemory:(BOOL)storeInMemory + callback:(void (^)(id object, NSError *))callback +{ + APStorageTask *task = [self.taskManager addTaskWithURL:url storeInMemory:storeInMemory + callbackBlock:callback]; + if (task.isShouldRun) { __weak __typeof(self) weakSelf = self; - [self objectFromStorageWithURL:url callback:^(id object, NSError *error) + [self storageObjectForTask:task callback:^(id object, NSError *error) { [weakSelf.taskManager finishTaskWithURL:url object:object error:error]; }]; @@ -79,8 +85,14 @@ - (void)loadObjectWithURL:(NSURL *)url callback:(void (^)(id object, NSError *)) - (void)reloadObjectWithURL:(NSURL *)url callback:(void (^)(id object, NSError *))callback { - [self.networkStorage cancelDownloadURL:url]; - [self objectFromNetworkWithURL:url callback:callback]; + [self reloadObjectWithURL:url storeInMemory:NO callback:callback]; +} + +- (void)reloadObjectWithURL:(NSURL *)url storeInMemory:(BOOL)storeInMemory + callback:(void (^)(id object, NSError *))callback +{ + [self removeObjectWithURLFromStorage:url]; + [self loadObjectWithURL:url storeInMemory:storeInMemory callback:callback]; } - (void)removeObjectWithURLFromMemory:(NSURL *)url @@ -162,22 +174,24 @@ - (APNetworkStorage *)networkStorage #pragma mark - private methods -- (void)objectFromStorageWithURL:(NSURL *)url callback:(void (^)(id object, NSError *error))callback +- (void)storageObjectForTask:(APStorageTask *)task callback:(void (^)(id object, NSError *error))callback { __weak __typeof(self) weakSelf = self; - [self objectFromMemoryWithURL:url callback:^(id object) + [self memoryObjectForTask:task callback:^(id object) { - object ? callback(object, nil) : [weakSelf objectFromFileWithURL:url callback:callback]; + object ? callback(object, nil) : [weakSelf fileObjectForTask:task callback:callback]; }]; } -- (void)objectFromMemoryWithURL:(NSURL *)objectURL callback:(void (^)(id object))callback +- (void)memoryObjectForTask:(APStorageTask *)task callback:(void (^)(id object))callback { - callback([self.memoryStorage objectWithURL:objectURL]); + callback([self.memoryStorage objectWithURL:task.url]); } -- (void)objectFromFileWithURL:(NSURL *)url callback:(void (^)(id object, NSError *error))callback +- (void)fileObjectForTask:(APStorageTask *)task callback:(void (^)(id object, NSError *error))callback { + NSURL *url = task.url; + BOOL shouldStoreInMemory = task.storeInMemory; __weak __typeof(self) weakSelf = self; [self.fileStorage dataWithURL:url callback:^(NSData *data) { @@ -185,29 +199,33 @@ - (void)objectFromFileWithURL:(NSURL *)url callback:(void (^)(id object, NSError if (data) { id result = weakSelf.parsingBlock ? weakSelf.parsingBlock(data, url) : data; - [weakSelf.memoryStorage setObject:result forURL:url]; + if (shouldStoreInMemory) + { + [weakSelf.memoryStorage setObject:result forURL:url]; + } callback(result, nil); } // if no data then load from network else { - [weakSelf objectFromNetworkWithURL:url callback:callback]; + [weakSelf networkObjectForTask:task callback:callback]; } }]; } -- (void)objectFromNetworkWithURL:(NSURL *)url callback:(void (^)(id object, NSError *error))callback +- (void)networkObjectForTask:(APStorageTask *)task callback:(void (^)(id object, NSError *error))callback { + NSURL *url = task.url; __weak __typeof(self) weakSelf = self; [self.networkStorage downloadURL:url callback:^(NSString *path, NSError *networkError) { // performed in background thread! NSError *fileError = nil; // file has been downloaded and moved - if (path && !networkError && [weakSelf.fileStorage moveDataWithURL:url downloadedToPath:path - error:&fileError]) + if (path && !networkError && + [weakSelf.fileStorage moveDataWithURL:url downloadedToPath:path error:&fileError]) { - [weakSelf objectFromFileWithURL:url callback:callback]; + [weakSelf fileObjectForTask:task callback:callback]; } else { diff --git a/Spec/APSmartStorageSpec.xcodeproj/project.pbxproj b/Spec/APSmartStorageSpec.xcodeproj/project.pbxproj index ba8401d..c1ddde8 100644 --- a/Spec/APSmartStorageSpec.xcodeproj/project.pbxproj +++ b/Spec/APSmartStorageSpec.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ F6841D59D49E4FD4AFFA475C /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DDE27DF55FBD4DDB9FFA4B1C /* libPods.a */; }; + FA00E22118EC2DFB00723260 /* APSmartStorage+Memory.m in Sources */ = {isa = PBXBuildFile; fileRef = FA00E22018EC2DFB00723260 /* APSmartStorage+Memory.m */; }; FA68F29A188FC56E007CCE3E /* APSmartStorageBaseSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = FA68F299188FC56E007CCE3E /* APSmartStorageBaseSpec.mm */; }; FA68F29C188FC57E007CCE3E /* APSmartStorageFileSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = FA68F29B188FC57E007CCE3E /* APSmartStorageFileSpec.mm */; }; FA68F29E188FC58D007CCE3E /* APSmartStorageRemoveSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = FA68F29D188FC58D007CCE3E /* APSmartStorageRemoveSpec.mm */; }; @@ -34,6 +35,8 @@ /* Begin PBXFileReference section */ 3623D18215C44D7FA210E476 /* Pods.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.xcconfig; path = Pods/Pods.xcconfig; sourceTree = ""; }; DDE27DF55FBD4DDB9FFA4B1C /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; + FA00E21F18EC2DFB00723260 /* APSmartStorage+Memory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "APSmartStorage+Memory.h"; sourceTree = ""; }; + FA00E22018EC2DFB00723260 /* APSmartStorage+Memory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "APSmartStorage+Memory.m"; sourceTree = ""; }; FA68F299188FC56E007CCE3E /* APSmartStorageBaseSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = APSmartStorageBaseSpec.mm; sourceTree = ""; }; FA68F29B188FC57E007CCE3E /* APSmartStorageFileSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = APSmartStorageFileSpec.mm; sourceTree = ""; }; FA68F29D188FC58D007CCE3E /* APSmartStorageRemoveSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = APSmartStorageRemoveSpec.mm; sourceTree = ""; }; @@ -91,6 +94,8 @@ children = ( FA68F2A3188FE84E007CCE3E /* OHHTTPStubs+AllRequests.h */, FA68F2A4188FE84E007CCE3E /* OHHTTPStubs+AllRequests.m */, + FA00E21F18EC2DFB00723260 /* APSmartStorage+Memory.h */, + FA00E22018EC2DFB00723260 /* APSmartStorage+Memory.m */, ); name = Categories; sourceTree = ""; @@ -357,6 +362,7 @@ buildActionMask = 2147483647; files = ( FA68F29C188FC57E007CCE3E /* APSmartStorageFileSpec.mm in Sources */, + FA00E22118EC2DFB00723260 /* APSmartStorage+Memory.m in Sources */, FA6E6FC21887B6C300E8DF46 /* main.m in Sources */, FAD37B88189110F000825EB6 /* NSFileManager+Storage.m in Sources */, FA68F2A5188FE84E007CCE3E /* OHHTTPStubs+AllRequests.m in Sources */, diff --git a/Spec/APSmartStorageSpec/APSmartStorage+Memory.h b/Spec/APSmartStorageSpec/APSmartStorage+Memory.h new file mode 100644 index 0000000..2c5cc62 --- /dev/null +++ b/Spec/APSmartStorageSpec/APSmartStorage+Memory.h @@ -0,0 +1,15 @@ +// +// APSmartStorage+Memory.h +// APSmartStorageSpec +// +// Created by Alexey Belkevich on 4/2/14. +// Copyright (c) 2014 alterplay. All rights reserved. +// + +#import "APSmartStorage.h" + +@interface APSmartStorage (Memory) + +- (void)objectFromMemoryWithURL:(NSURL *)objectURL callback:(void (^)(id object))callback; + +@end diff --git a/Spec/APSmartStorageSpec/APSmartStorage+Memory.m b/Spec/APSmartStorageSpec/APSmartStorage+Memory.m new file mode 100644 index 0000000..b4124de --- /dev/null +++ b/Spec/APSmartStorageSpec/APSmartStorage+Memory.m @@ -0,0 +1,24 @@ +// +// APSmartStorage+Memory.m +// APSmartStorageSpec +// +// Created by Alexey Belkevich on 4/2/14. +// Copyright (c) 2014 alterplay. All rights reserved. +// + +#import "APSmartStorage+Memory.h" +#import "APStorageTask.h" + +@interface APSmartStorage (Private) +- (void)memoryObjectForTask:(APStorageTask *)task callback:(void (^)(id object))callback; +@end + +@implementation APSmartStorage (Memory) + +- (void)objectFromMemoryWithURL:(NSURL *)objectURL callback:(void (^)(id object))callback +{ + APStorageTask *task = [[APStorageTask alloc] initWithTaskURL:objectURL]; + [self memoryObjectForTask:task callback:callback]; +} + +@end diff --git a/Spec/APSmartStorageSpec/APSmartStorageBaseSpec.mm b/Spec/APSmartStorageSpec/APSmartStorageBaseSpec.mm index 009a5ea..e60b6ec 100644 --- a/Spec/APSmartStorageSpec/APSmartStorageBaseSpec.mm +++ b/Spec/APSmartStorageSpec/APSmartStorageBaseSpec.mm @@ -7,16 +7,12 @@ // #import "CedarAsync.h" -#import "APSmartStorage.h" +#import "APSmartStorage+Memory.h" #import "OHHTTPStubs+AllRequests.h" using namespace Cedar::Matchers; using namespace Cedar::Doubles; -@interface APSmartStorage (Private) -- (void)objectFromMemoryWithURL:(NSURL *)objectURL callback:(void (^)(id object))callback; -@end - SPEC_BEGIN(APSmartStorageBaseSpec) describe(@"APSmartStorage", ^ @@ -110,6 +106,41 @@ - (void)objectFromMemoryWithURL:(NSURL *)objectURL callback:(void (^)(id object) in_time(checkObject) should equal(responseObject); }); + it(@"should load object from network write it to file and don't store it in memory", ^ + { + __block id loadedObject = [[NSObject alloc] init]; + __block id memoryObject = [[NSObject alloc] init]; + __block BOOL fileExists = NO; + [storage loadObjectWithURL:objectURL storeInMemory:NO callback:^(id object, NSError *error) + { + loadedObject = object; + fileExists = [NSFileManager.defaultManager fileExistsAtPath:filePath]; + // loading object from memory + [storage objectFromMemoryWithURL:objectURL callback:^(id obj) + { + memoryObject = obj; + }]; + }]; + in_time(fileExists) should equal(YES); + in_time(memoryObject) should be_nil; + in_time(loadedObject) should equal(responseObject); + }); + + it(@"should load object and store it in memory if at least one call needs to store", ^ + { + __block id memoryObject = [[NSObject alloc] init]; + [storage loadObjectWithURL:objectURL storeInMemory:NO callback:^(id object, NSError *error) + { + // loading object from memory + [storage objectFromMemoryWithURL:objectURL callback:^(id obj) + { + memoryObject = obj; + }]; + }]; + [storage loadObjectWithURL:objectURL storeInMemory:YES callback:nil]; + in_time(memoryObject) should equal(responseObject); + }); + it(@"should parse loaded object with block", ^ { // parsing block diff --git a/Spec/APSmartStorageSpec/APSmartStorageFileSpec.mm b/Spec/APSmartStorageSpec/APSmartStorageFileSpec.mm index 6895696..1bcbbfc 100644 --- a/Spec/APSmartStorageSpec/APSmartStorageFileSpec.mm +++ b/Spec/APSmartStorageSpec/APSmartStorageFileSpec.mm @@ -7,16 +7,12 @@ // #import "CedarAsync.h" -#import "APSmartStorage.h" +#import "APSmartStorage+Memory.h" #import "OHHTTPStubs+AllRequests.h" using namespace Cedar::Matchers; using namespace Cedar::Doubles; -@interface APSmartStorage (Private) -- (void)objectFromMemoryWithURL:(NSURL *)objectURL callback:(void (^)(id object))callback; -@end - SPEC_BEGIN(APSmartStorageFileSpec) describe(@"APSmartStorage", ^ diff --git a/Spec/APSmartStorageSpec/APSmartStorageRemoveSpec.mm b/Spec/APSmartStorageSpec/APSmartStorageRemoveSpec.mm index aa30c74..ef3601f 100644 --- a/Spec/APSmartStorageSpec/APSmartStorageRemoveSpec.mm +++ b/Spec/APSmartStorageSpec/APSmartStorageRemoveSpec.mm @@ -7,14 +7,13 @@ // #import "CedarAsync.h" -#import "APSmartStorage.h" +#import "APSmartStorage+Memory.h" #import "OHHTTPStubs+AllRequests.h" using namespace Cedar::Matchers; using namespace Cedar::Doubles; @interface APSmartStorage (Private) -- (void)objectFromMemoryWithURL:(NSURL *)objectURL callback:(void (^)(id object))callback; - (void)didReceiveMemoryWarning:(NSNotification *)notification; @end diff --git a/Spec/APSmartStorageSpec/APSmartStorageTaskSpec.mm b/Spec/APSmartStorageSpec/APSmartStorageTaskSpec.mm index f932f72..6cefd89 100644 --- a/Spec/APSmartStorageSpec/APSmartStorageTaskSpec.mm +++ b/Spec/APSmartStorageSpec/APSmartStorageTaskSpec.mm @@ -45,7 +45,7 @@ [OHHTTPStubs removeAllStubs]; }); - it(@"should call 2 callbacks on first load", ^ + it(@"should call 2 callbacks if URL download executed 2 times before complete", ^ { // loading object and check file and memory __block id checkObject1 = [[NSObject alloc] init];