From b9097361ab61584ea7924f3888c05e1678146af3 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Fri, 22 Mar 2024 14:50:39 -0400 Subject: [PATCH] Digital PCT265 story MVAPCT-48 - Caching piping improvements --- .../MVMCoreCache+Extension.swift | 27 +++- MVMCore/MVMCore/OtherHandlers/MVMCoreCache.h | 19 ++- MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m | 129 ++++++++++++------ 3 files changed, 125 insertions(+), 50 deletions(-) diff --git a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache+Extension.swift b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache+Extension.swift index ac03a82..8658be3 100644 --- a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache+Extension.swift +++ b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache+Extension.swift @@ -85,15 +85,34 @@ public class CachedData: Codable { // cache.setObject(decodedCachedData, forKey: keyNSString) return decodedCachedData.data } else { - throw CacheError.dataExpired + throw CacheError.dataExpired } - } catch let error as DecodingError { - throw CacheError.deserializationFailed } catch { - throw CacheError.loadFailed(error) + // Remove item from the cache on any failure. + try fileManager.removeItem(at: filePath) + + switch error { + case is DecodingError: + throw CacheError.deserializationFailed + default: + throw CacheError.loadFailed(error) + } } // } } + @objc public func remove(forKey key: String) throws { + let filePath = self.filePath(forKey: key) + try fileManager.removeItem(at: filePath) + } + + @objc public func removeAll() throws { + let fileURLs = try fileManager.contentsOfDirectory(at: documentsDirectory, + includingPropertiesForKeys: nil, + options: .skipsHiddenFiles) + for fileURL in fileURLs where fileURL.pathExtension == "json" { + try FileManager.default.removeItem(at: fileURL) + } + } private func filePath(forKey key: String) -> URL { return documentsDirectory.appendingPathComponent("\(key).json") diff --git a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.h b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.h index 3d0365d..945e22a 100644 --- a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.h +++ b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.h @@ -31,14 +31,17 @@ typedef void(^MVMCoreGetImageBlock)(UIImage * _Nullable, NSData * _Nullable, BOO #pragma mark - Page and Module Handling // Checks the set of pageTypes to be cached for the given pageType. -- (BOOL)shouldCacheJSONWithPageType:(nonnull NSString *)pageType; +- (BOOL)shouldCachePageJSON:(nonnull NSDictionary *)jsonDictionary pageType:(nonnull NSString *)pageType; // Checks the set of modules to be cached for the given module -- (BOOL)shouldCacheJSONWithModule:(nonnull NSString *)module; +- (BOOL)shouldCacheModuleJSON:(nonnull NSDictionary *)jsonDictionary moduleName:(nonnull NSString *)moduleName; /// Returns if the json is expired or not. - (BOOL)isJSONExpired:(nonnull NSDictionary *)jsonDictionary; +/// Returns the expiry time for the object. +- (NSTimeInterval)getExpiryForJSON:(nonnull NSDictionary *)jsonDictionary; + /// Checks if the page is to be persistently cached. - (BOOL)shouldPersistentlyCachePage:(nonnull NSDictionary *)jsonDictionary pageType:(nonnull NSString *)pageType; @@ -62,6 +65,12 @@ typedef void(^MVMCoreGetImageBlock)(UIImage * _Nullable, NSData * _Nullable, BOO // Gets a json dictionary from the cache with all the requested modules. Pass in the block that you want to run once the dictionary is received. This will be run on a background thread. - (void)fetchJSONForModules:(nullable NSArray *)modules completionHandler:(nonnull void (^)(NSDictionary * _Nullable jsonDictionary))completionHandler; +/// Returns a page JSON from the persistent cache. +- (NSDictionary * _Nullable)fetchPageFromPersistentCache:(nonnull NSString *)pageType; + +/// Returns a module JSON from the persistent cache. +- (NSDictionary * _Nullable)fetchModuleFromPersistentCache:(nonnull NSString *)moduleName; + #pragma mark - Advanced Fetch // Pass in the block that you want to run once the dictionary is received and which queue to run it on. Pass in if you'd like the current thread to wait. @@ -84,6 +93,9 @@ typedef void(^MVMCoreGetImageBlock)(UIImage * _Nullable, NSData * _Nullable, BOO // Adds a json dictionary to the cache by modules. - (void)addModulesToCache:(nonnull NSDictionary *)jsonDictionary; +/// Adds the json to the persistent cache by pageType. +- (void)addPageToPersistentCache:(nonnull NSDictionary *)jsonDictionary pageType:(nonnull NSString *)pageType expiry:(NSTimeInterval)expiry; + #pragma mark - Advanced Insertion // Adds a json dictionary to the cache by pageType. Pass in the block that you want to run once the dictionary is received and which queue to run it on. Pass in if you'd like the current thread to wait. @@ -120,6 +132,9 @@ typedef void(^MVMCoreGetImageBlock)(UIImage * _Nullable, NSData * _Nullable, BOO // Removes the json for modules. Pass in the block that you want to run once the dictionary is received and which queue to run it on. - (void)removeJSONForModules:(nonnull NSArray *)modules queue:(nullable NSOperationQueue *)queue waitUntilFinished:(BOOL)waitUntilFinished completionBlock:(nullable void (^)(void))completionBlock; +/// Clears the persistent JSON cache +- (void)clearPersistentJSONCache; + #pragma mark Image Functions /// Register a bundle as one to search for images in. diff --git a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m index 8fd3a82..b0fac90 100644 --- a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m +++ b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m @@ -91,20 +91,28 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; return _modulesToNotCache; } -- (BOOL)shouldCacheJSONWithPageType:(nonnull NSString *)pageType { - return ![self.pageTypesToNotCache containsObject:pageType]; +- (BOOL)shouldCachePageJSON:(nonnull NSDictionary *)jsonDictionary pageType:(nonnull NSString *)pageType { + if ([self.pageTypesToNotCache containsObject:pageType]) { return NO; } + NSNumber *shouldCache = [jsonDictionary optionalNumberForKey:@"cache"]; + return shouldCache == nil || shouldCache.boolValue; } -- (BOOL)shouldCacheJSONWithModule:(nonnull NSString *)module { - return ![self.modulesToNotCache containsObject:module]; +- (BOOL)shouldCacheModuleJSON:(nonnull NSDictionary *)jsonDictionary moduleName:(nonnull NSString *)moduleName { + if ([self.modulesToNotCache containsObject:moduleName]) { return NO; } + NSNumber *shouldCache = [jsonDictionary optionalNumberForKey:@"cache"]; + return shouldCache == nil || shouldCache.boolValue; } - (BOOL)isJSONExpired:(nonnull NSDictionary *)jsonDictionary { - double expiryTime = [[jsonDictionary dict:@"expiry"] doubleValue] * 1000; + NSTimeInterval expiryTime = [self getExpiryForJSON:jsonDictionary]; NSTimeInterval timeSinceUnixEpoc = [[NSDate date] timeIntervalSince1970]; return timeSinceUnixEpoc > expiryTime; } +- (NSTimeInterval)getExpiryForJSON:(nonnull NSDictionary *)jsonDictionary { + return [[jsonDictionary dict:@"expiry"] doubleValue] * 1000.0; +} + - (BOOL)shouldPersistentlyCacheJSON:(nonnull NSDictionary *)jsonDictionary { NSDictionary *cachePolicy = [jsonDictionary dict:@"cachePolicy"]; if (!cachePolicy || ![cachePolicy boolForKey:@"persist"]) { @@ -147,6 +155,16 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; [self fetchJSONForModules:modules queue:self.completionQueue waitUntilFinished:NO completionHandler:completionHandler]; } +- (NSDictionary * _Nullable)fetchPageFromPersistentCache:(nonnull NSString *)pageType { + NSError *error = nil; + return [[DataCacheManager shared] loadForKey:pageType error:&error]; +} + +- (NSDictionary * _Nullable)fetchModuleFromPersistentCache:(nonnull NSString *)moduleName { + NSError *error = nil; + return [[DataCacheManager shared] loadForKey:moduleName error:&error]; +} + #pragma mark - Advanced Fetch - (void)fetchJSONForPageType:(nullable NSString *)pageType queue:(nullable NSOperationQueue *)queue waitUntilFinished:(BOOL)waitUntilFinished completionHandler:(nonnull void (^)(NSDictionary * _Nullable jsonDictionary))completionHandler { @@ -163,8 +181,7 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; // First checks the cache by page type. dictionary = [weakSelf.pageTypeCache objectForKey:pageType]; if (!dictionary) { - NSError *error = nil; - dictionary = [[DataCacheManager shared] loadForKey:pageType error:&error]; + dictionary = [self fetchPageFromPersistentCache:pageType]; } } else { @@ -203,8 +220,7 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; if (moduleDictionary) { [modulesDictionary setObject:moduleDictionary forKey:module]; } else { - NSError *error = nil; - moduleDictionary = [[DataCacheManager shared] loadForKey:module error:&error]; + moduleDictionary = [self fetchModuleFromPersistentCache:module]; if (moduleDictionary) { [modulesDictionary setObject:moduleDictionary forKey:module]; } @@ -238,16 +254,31 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; [self addModulesToCache:jsonDictionary queue:nil waitUntilFinished:NO completionBlock:NULL]; } +- (void)addPageToPersistentCache:(nonnull NSDictionary *)jsonDictionary pageType:(nonnull NSString *)pageType expiry:(NSTimeInterval)expiry { + NSError *error = nil; + [[DataCacheManager shared] saveWithData:jsonDictionary forKey:pageType cacheDuration:expiry shouldPersist:YES error:&error]; +} + +- (void)addModuleToPersistentCache:(nonnull NSDictionary *)jsonDictionary moduleName:(nonnull NSString *)moduleName expiry:(NSTimeInterval)expiry { + NSError *error = nil; + [[DataCacheManager shared] saveWithData:jsonDictionary forKey:moduleName cacheDuration:expiry shouldPersist:YES error:&error]; +} + #pragma mark - Advanced Insertion - (void)addPageToCache:(nonnull NSDictionary *)jsonDictionary pageType:(nonnull NSString *)pageType queue:(nullable NSOperationQueue *)queue waitUntilFinished:(BOOL)waitUntilFinished completionBlock:(nullable void (^)(void))completionBlock { - + if (![self shouldCachePageJSON:jsonDictionary pageType:pageType]) { + if (completionBlock) { + completionBlock(); + } + return; + } NSBlockOperation *addOperation = [[NSBlockOperation alloc] init]; __weak NSBlockOperation *weakOperation = addOperation; __weak typeof(self) weakSelf = self; [addOperation addExecutionBlock:^{ - if (!weakOperation.isCancelled && [[MVMCoreCache sharedCache] shouldCacheJSONWithPageType:pageType]) { + if (!weakOperation.isCancelled) { // There must be a dictionary and page type to cache. if (jsonDictionary && pageType) { @@ -259,14 +290,13 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; } // Adds json to cache with page type key. - NSNumber *shouldCache = [jsonDictionary optionalNumberForKey:@"cache"]; - if (shouldCache == nil || shouldCache.boolValue) { - [weakSelf.pageTypeCache setObject:jsonDictionary forKey:pageType]; - if ([self shouldPersistentlyCachePage:jsonDictionary pageType:pageType]) { - NSError *error = nil; - [[DataCacheManager shared] saveWithData:jsonDictionary forKey:pageType cacheDuration:604800 shouldPersist:YES error:&error]; - } + [weakSelf.pageTypeCache setObject:jsonDictionary forKey:pageType]; + + if (![self shouldPersistentlyCachePage:jsonDictionary pageType:pageType]) { + [[DataCacheManager shared] removeForKey:pageType error:nil]; + return; } + [self addPageToPersistentCache:jsonDictionary pageType:pageType expiry:[self getExpiryForJSON:jsonDictionary]]; } } if (completionBlock) { @@ -277,11 +307,12 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; } - (void)addModuleToCache:(nonnull NSDictionary *)jsonDictionary module:(nonnull NSString *)module queue:(nullable NSOperationQueue *)queue waitUntilFinished:(BOOL)waitUntilFinished completionBlock:(nullable void (^)(void))completionBlock { - [self addModulesToCache:@{module:jsonDictionary} queue:queue waitUntilFinished:waitUntilFinished completionBlock:completionBlock]; -} - -- (void)addModulesToCache:(nonnull NSDictionary *)jsonDictionary queue:(nullable NSOperationQueue *)queue waitUntilFinished:(BOOL)waitUntilFinished completionBlock:(nullable void (^)(void))completionBlock { - + if (![self shouldCacheModuleJSON:jsonDictionary moduleName:module]) { + if (completionBlock) { + completionBlock(); + } + return; + } NSBlockOperation *addOperation = [[NSBlockOperation alloc] init]; __weak NSBlockOperation *weakOperation = addOperation; __weak typeof(self) weakSelf = self; @@ -289,28 +320,19 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; if (!weakOperation.isCancelled) { - [jsonDictionary enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { + if (!weakSelf.moduleCache) { - if ([[MVMCoreCache sharedCache] shouldCacheJSONWithModule:key]) { - - if (!weakSelf.moduleCache) { - - // Create the cache if necessary. - weakSelf.moduleCache = [NSMutableDictionary dictionary]; - } - - // Adds json to cache with page type key. - NSNumber *shouldCache = [jsonDictionary optionalNumberForKey:@"cache"]; - if (shouldCache == nil || shouldCache.boolValue) { - [weakSelf.moduleCache setObject:obj forKey:key]; - - if ([self shouldPersistentlyCacheModule:obj module:key]) { - NSError *error = nil; - [[DataCacheManager shared] saveWithData:obj forKey:key cacheDuration:604800 shouldPersist:YES error:&error]; - } - } - } - }]; + // Create the cache if necessary. + weakSelf.moduleCache = [NSMutableDictionary dictionary]; + } + + [weakSelf.moduleCache setObject:jsonDictionary forKey:module]; + + if (![self shouldPersistentlyCacheModule:jsonDictionary module:module]) { + [[DataCacheManager shared] removeForKey:module error:nil]; + return; + } + [self addModuleToPersistentCache:jsonDictionary moduleName:module expiry:[self getExpiryForJSON:jsonDictionary]]; } if (completionBlock) { [(queue ?: weakSelf.completionQueue) addOperations:@[[NSBlockOperation blockOperationWithBlock:completionBlock]] waitUntilFinished:waitUntilFinished]; @@ -319,6 +341,18 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; [self.moduleQueue addOperations:@[addOperation] waitUntilFinished:waitUntilFinished]; } +- (void)addModulesToCache:(nonnull NSDictionary *)jsonDictionary queue:(nullable NSOperationQueue *)queue waitUntilFinished:(BOOL)waitUntilFinished completionBlock:(nullable void (^)(void))completionBlock { + [jsonDictionary enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { + [self addModuleToCache:obj module:key]; + }]; + __weak typeof(self) weakSelf = self; + [self.moduleQueue addOperations:@[[NSBlockOperation blockOperationWithBlock:^{ + if (completionBlock) { + [(queue ?: weakSelf.completionQueue) addOperations:@[[NSBlockOperation blockOperationWithBlock:completionBlock]] waitUntilFinished:waitUntilFinished]; + } + }]] waitUntilFinished:waitUntilFinished]; +} + #pragma mark - Simple Deletion - (void)removeJSONForPageType:(nonnull NSString *)pageType { @@ -333,6 +367,10 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; [self removeJSONForModules:modules queue:nil waitUntilFinished:NO completionBlock:NULL]; } +- (void)clearPersistentJSONCache { + [[DataCacheManager shared] removeAllAndReturnError:nil]; +} + - (void)clearMFCache { [self.pageTypeQueue cancelAllOperations]; [self.moduleQueue cancelAllOperations]; @@ -370,6 +408,7 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; } }]; [self.pageTypeQueue addOperations:@[removeOperation] waitUntilFinished:waitUntilFinished]; + [[DataCacheManager shared] removeForKey:pageType error:nil]; } - (void)removeJSONForModule:(nonnull NSString *)module queue:(nullable NSOperationQueue *)queue waitUntilFinished:(BOOL)waitUntilFinished completionBlock:(nullable void (^)(void))completionBlock { @@ -393,6 +432,7 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; } }]; [self.moduleQueue addOperations:@[removeOperation] waitUntilFinished:waitUntilFinished]; + [[DataCacheManager shared] removeForKey:module error:nil]; } - (void)removeJSONForModules:(nonnull NSArray *)modules queue:(nullable NSOperationQueue *)queue waitUntilFinished:(BOOL)waitUntilFinished completionBlock:(nullable void (^)(void))completionBlock { @@ -412,6 +452,7 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; // Removes json from cache with module key. [weakSelf.moduleCache removeObjectForKey:obj]; } + [[DataCacheManager shared] removeForKey:obj error:nil]; } }]; if (completionBlock) {