From 8c32dbbd7d3616a75ec9bf6cf08e3776b7a549b6 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Wed, 20 Mar 2024 19:09:13 -0400 Subject: [PATCH 01/15] Digital PCT265 story MVAPCT-48 - Initial demo of loading feed from cache --- MVMCore/MVMCore.xcodeproj/project.pbxproj | 4 + .../MVMCore/LoadHandling/MVMCoreLoadHandler.m | 6 + ...VMCoreLoadRequestOperation+Extension.swift | 32 +++++ .../MVMCoreLoadRequestOperation.h | 2 + .../MVMCoreLoadRequestOperation.m | 122 ++++++++++-------- .../MVMCoreCache+Extension.swift | 101 +++++++++++++++ MVMCore/MVMCore/OtherHandlers/MVMCoreCache.h | 2 + MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m | 30 +++++ 8 files changed, 242 insertions(+), 57 deletions(-) create mode 100644 MVMCore/MVMCore/OtherHandlers/MVMCoreCache+Extension.swift diff --git a/MVMCore/MVMCore.xcodeproj/project.pbxproj b/MVMCore/MVMCore.xcodeproj/project.pbxproj index da71c4e..c827f0f 100644 --- a/MVMCore/MVMCore.xcodeproj/project.pbxproj +++ b/MVMCore/MVMCore.xcodeproj/project.pbxproj @@ -96,6 +96,7 @@ AF43A7411FC5FA6F008E9347 /* MVMCoreViewProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = AF43A7401FC5FA6F008E9347 /* MVMCoreViewProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; AF43A74C1FC6109F008E9347 /* MVMCoreSessionObject.h in Headers */ = {isa = PBXBuildFile; fileRef = AF43A74A1FC6109F008E9347 /* MVMCoreSessionObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; AF43A74D1FC6109F008E9347 /* MVMCoreSessionObject.m in Sources */ = {isa = PBXBuildFile; fileRef = AF43A74B1FC6109F008E9347 /* MVMCoreSessionObject.m */; }; + AF4955E22BAB1EB200567276 /* MVMCoreCache+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF4955E12BAB1EB200567276 /* MVMCoreCache+Extension.swift */; }; AF60A7F2289212CA00919EEB /* MVMError.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF60A7F1289212CA00919EEB /* MVMError.swift */; }; AF60A7F4289212EB00919EEB /* MVMCoreError.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF60A7F3289212EB00919EEB /* MVMCoreError.swift */; }; AF686FDA2A8A876A008F666A /* NavigationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF686FD92A8A876A008F666A /* NavigationOperation.swift */; }; @@ -252,6 +253,7 @@ AF43A7401FC5FA6F008E9347 /* MVMCoreViewProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreViewProtocol.h; sourceTree = ""; }; AF43A74A1FC6109F008E9347 /* MVMCoreSessionObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreSessionObject.h; sourceTree = ""; }; AF43A74B1FC6109F008E9347 /* MVMCoreSessionObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreSessionObject.m; sourceTree = ""; }; + AF4955E12BAB1EB200567276 /* MVMCoreCache+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreCache+Extension.swift"; sourceTree = ""; }; AF60A7F1289212CA00919EEB /* MVMError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMError.swift; sourceTree = ""; }; AF60A7F3289212EB00919EEB /* MVMCoreError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreError.swift; sourceTree = ""; }; AF686FD92A8A876A008F666A /* NavigationOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationOperation.swift; sourceTree = ""; }; @@ -656,6 +658,7 @@ children = ( AF43A7091FC4F415008E9347 /* MVMCoreCache.h */, AF43A7081FC4F415008E9347 /* MVMCoreCache.m */, + AF4955E12BAB1EB200567276 /* MVMCoreCache+Extension.swift */, 605A9A292ABD712F00487E47 /* MVMCoreLoggingHandler.swift */, 6042E8FB2B3094680031644B /* MVMCoreLoggingHandlerHelper.h */, D288D5F426C6EFE000A5C365 /* MVMCoreLoggingHandler+Extension.swift */, @@ -902,6 +905,7 @@ AFBB96351FBA34310008D868 /* MVMCoreErrorConstants.m in Sources */, AF43A5881FBB67D6008E9347 /* MVMCoreActionUtility.m in Sources */, AFED77A61FCCA29400BAE689 /* MVMCoreViewControllerStoryBoardMappingObject.m in Sources */, + AF4955E22BAB1EB200567276 /* MVMCoreCache+Extension.swift in Sources */, 016CF36925FA6DD400B82A1F /* ClientParameterHandler.swift in Sources */, 5846ABF42B44BB9000FA6C76 /* Collection+Safe.swift in Sources */, AF69D4F7286EA0B800BC6862 /* ActionPreviousSubmitHandler.swift in Sources */, diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m index 4e39318..af912a4 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m @@ -383,8 +383,14 @@ if (requestParameters.backgroundRequest) { return [self loadBackgroundRequest:requestParameters dataForPage:dataForPage delegateObject:delegateObject]; } else { + if (!requestParameters.noloadingOverlay) { + [[MVMCoreLoadingOverlayHandler sharedLoadingOverlay] startLoading]; + } MVMCoreLoadRequestOperation *loadOperation = [[MVMCoreLoadRequestOperation alloc] initWithRequestParameters:requestParameters dataForPage:dataForPage delegateObject:delegateObject backgroundLoad:NO]; loadOperation.identifier = requestParameters.identifier; + [loadOperation setCompletionBlock:^{ + [[MVMCoreLoadingOverlayHandler sharedLoadingOverlay] stopLoading:YES]; + }]; [self.blockingLoadQueue addOperation:loadOperation]; return loadOperation; } diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation+Extension.swift b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation+Extension.swift index 181dae8..d3b4ea1 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation+Extension.swift +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation+Extension.swift @@ -38,6 +38,38 @@ public enum PopBackError: MVMError, CustomStringConvertible { @objc public extension MVMCoreLoadRequestOperation { + + @objc + @MainActor + func checkIfNewControllerIsNeeded(loadObject: MVMCoreLoadObject) -> Bool { + guard loadObject.requestParameters?.replaceViewIfOnStackElseLoadWithStyle == true, + let pageType = loadObject.pageType else { + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: Controller shouldn't be replaced.") + return true + } + let newVC = MVMCoreViewControllerMappingObject.shared()?.createMFViewController(ofTemplate: loadObject.pageJSON?.optionalStringForKey("template"), pageType: pageType) + guard let index = NavigationHandler.shared().navigationController?.viewControllers.firstIndex(where: { controller in + (controller as? MVMCoreViewControllerProtocol)?.pageType == pageType && type(of: controller) == type(of: newVC) + }) else { + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: No matching controller found.") + return true + } + if index == NavigationHandler.shared().navigationController!.viewControllers.count - 1 { + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: Controller is already showing.") + Task { + MVMCoreLoadRequestOperation.loadFinished(loadObject, loadedViewController: nil, errorObject: nil) + } + } else { + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: Pop back to controller.") + guard let operation = try? NavigationHandler.shared().getOperationPopToViewController(with: pageType, navigationController: loadObject.requestParameters?.navigationController, delegateObject: loadObject.delegateObject, animated: !(loadObject.requestParameters?.shouldNotAnimatePush ?? false)) else { return true } + Task { + await navigate(with: operation, loadObject: loadObject) + MVMCoreLoadRequestOperation.loadFinished(loadObject, loadedViewController: nil, errorObject: nil) + } + } + return false + } + @objc func popBackToPage(for loadObject: MVMCoreLoadObject) { Task(priority: .high) { diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.h b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.h index fa034e9..fe334b3 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.h +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.h @@ -90,6 +90,8 @@ */ + (void)removeCaches:(nullable NSDictionary *)cacheDictionary; ++ (void)notifyListenersOfNewResponse:(nullable NSDictionary *)pages modules:(nullable NSDictionary *)modules systemParameters:(nullable NSDictionary *)systemParameters loadObject:(nonnull MVMCoreLoadObject *)loadObject; + /** Creates the view controller based on the load object passed in. * @param loadObject The load data from the cache or server. * @param completionHandler The completion handler to load once finished. Returns any loaded view controller and the load.*/ diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m index 4563544..3517361 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m @@ -132,71 +132,74 @@ } else { // No provided load object, check the cache for data first.. - [MVMCoreLoadRequestOperation checkCacheForDataForRequest:self.requestParameters completionHandler:^(NSDictionary *pageFromCache, NSDictionary *modulesFromCache) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ - if ([self checkAndHandleForCancellation]) { - return; - } - - // Log if loaded from cache. - if (pageFromCache) { - MVMCoreLog(@"loaded from cache page %@",[MVMCoreActionUtility formatDictionaryAsJSONString:pageFromCache]); - } - if (modulesFromCache) { - MVMCoreLog(@"loaded from cache modules %@",[MVMCoreActionUtility formatDictionaryAsJSONString:modulesFromCache]); - } - - // Create a load object from any data we fetched. - MVMCoreLoadObject *loadObject = [self createLoadObjectWithPageFromCache:pageFromCache modulesFromCache:modulesFromCache]; - - // Check if we need to go to server for missing data. - MVMCoreRequestParameters *requestForMissingData = [MVMCoreLoadRequestOperation createRequestForDataWithLoadObject:loadObject]; - if (!requestForMissingData) { + [MVMCoreLoadRequestOperation checkCacheForDataForRequest:self.requestParameters completionHandler:^(NSDictionary *pageFromCache, NSDictionary *modulesFromCache) { - [[MVMCoreLoggingHandler sharedLoggingHandler] logPageLoadCompleteFor:loadObject.requestParameters.pageType serverProcessingTime:@"0" requestURL:loadObject.requestParameters.URL.absoluteString requestUUID:loadObject.identifier isFromCache:loadObject.pageDataFromCache]; - - // We have all the needed data, continue with the load. - [MVMCoreLoadRequestOperation handleLoadObject:loadObject error:nil]; - } else { - - if(!self.backgroundLoad && loadObject.requestParameters.pageType) { - [[MVMCoreLoggingHandler sharedLoggingHandler] logPageLoadStartedFor:loadObject.requestParameters.pageType requestUUID:loadObject.identifier requestURL:loadObject.requestParameters.URL.absoluteString]; + if ([self checkAndHandleForCancellation]) { + return; } - - // Send a new request to the server. - [MVMCoreLoadRequestOperation sendRequest:requestForMissingData loadObject:loadObject completionHandler:^(NSDictionary * _Nullable json) { - + + // Log if loaded from cache. + if (pageFromCache) { + MVMCoreLog(@"loaded from cache page %@",[MVMCoreActionUtility formatDictionaryAsJSONString:pageFromCache]); + } + if (modulesFromCache) { + MVMCoreLog(@"loaded from cache modules %@",[MVMCoreActionUtility formatDictionaryAsJSONString:modulesFromCache]); + } + + // Create a load object from any data we fetched. + MVMCoreLoadObject *loadObject = [self createLoadObjectWithPageFromCache:pageFromCache modulesFromCache:modulesFromCache]; + + // Check if we need to go to server for missing data. + MVMCoreRequestParameters *requestForMissingData = [MVMCoreLoadRequestOperation createRequestForDataWithLoadObject:loadObject]; + if (!requestForMissingData) { + + [[MVMCoreLoggingHandler sharedLoggingHandler] logPageLoadCompleteFor:loadObject.requestParameters.pageType serverProcessingTime:@"0" requestURL:loadObject.requestParameters.URL.absoluteString requestUUID:loadObject.identifier isFromCache:loadObject.pageDataFromCache]; + + // We have all the needed data, continue with the load. + [MVMCoreLoadRequestOperation handleLoadObject:loadObject error:nil]; + } else { + + if(!self.backgroundLoad && loadObject.requestParameters.pageType) { + [[MVMCoreLoggingHandler sharedLoggingHandler] logPageLoadStartedFor:loadObject.requestParameters.pageType requestUUID:loadObject.identifier requestURL:loadObject.requestParameters.URL.absoluteString]; + } + + // Send a new request to the server. + [MVMCoreLoadRequestOperation sendRequest:requestForMissingData loadObject:loadObject completionHandler:^(NSDictionary * _Nullable json) { + #if ENABLE_HARD_CODED_RESPONSE - if ([[MVMCoreObject sharedInstance].globalLoadDelegate respondsToSelector:@selector(modifyJSON:)]) { - json = [[MVMCoreObject sharedInstance].globalLoadDelegate modifyJSON:json]; - } + if ([[MVMCoreObject sharedInstance].globalLoadDelegate respondsToSelector:@selector(modifyJSON:)]) { + json = [[MVMCoreObject sharedInstance].globalLoadDelegate modifyJSON:json]; + } #endif - - NSString *serverProcessTime = [(NSDictionary *)json objectChainOfKeysOrIndexes:@[@"ResponseInfo", @"timeStamp"]] ?: @"0"; - - if(!self.backgroundLoad && loadObject.requestParameters.pageType && serverProcessTime) { - [[MVMCoreLoggingHandler sharedLoggingHandler] logPageLoadCompleteFor:loadObject.requestParameters.pageType serverProcessingTime:serverProcessTime requestURL:loadObject.requestParameters.URL.absoluteString requestUUID:loadObject.identifier isFromCache:loadObject.pageDataFromCache]; - } - - // Process the data retrieved from the server. - [MVMCoreLoadRequestOperation processJSONFromServer:json loadObject:loadObject completionHandler:^(MVMCoreLoadObject * _Nonnull loadObject, MVMCoreErrorObject * _Nullable error) { - if ([loadObject.operation checkAndHandleForCancellation]) { - return; + NSString *serverProcessTime = [(NSDictionary *)json objectChainOfKeysOrIndexes:@[@"ResponseInfo", @"timeStamp"]] ?: @"0"; + + if(!self.backgroundLoad && loadObject.requestParameters.pageType && serverProcessTime) { + [[MVMCoreLoggingHandler sharedLoggingHandler] logPageLoadCompleteFor:loadObject.requestParameters.pageType serverProcessingTime:serverProcessTime requestURL:loadObject.requestParameters.URL.absoluteString requestUUID:loadObject.identifier isFromCache:loadObject.pageDataFromCache]; } - if (loadObject.pageDataFromCache || loadObject.pageType) { + // Process the data retrieved from the server. + [MVMCoreLoadRequestOperation processJSONFromServer:json loadObject:loadObject completionHandler:^(MVMCoreLoadObject * _Nonnull loadObject, MVMCoreErrorObject * _Nullable error) { - // Can continue loading with the page. - [MVMCoreLoadRequestOperation handleLoadObject:loadObject error:error]; - } else { - // Something to show, or nothing was expected to show, can finish. - [MVMCoreLoadRequestOperation loadFinished:loadObject loadedViewController:nil errorObject:error]; - } + if ([loadObject.operation checkAndHandleForCancellation]) { + return; + } + + if (loadObject.pageDataFromCache || loadObject.pageType) { + + // Can continue loading with the page. + [MVMCoreLoadRequestOperation handleLoadObject:loadObject error:error]; + } else { + // Something to show, or nothing was expected to show, can finish. + [MVMCoreLoadRequestOperation loadFinished:loadObject loadedViewController:nil errorObject:error]; + } + }]; }]; - }]; - } - }]; + } + }]; + }); } } @@ -581,9 +584,14 @@ }; if (!error.nativeDrivenErrorScreen) { - // Server driven screen, create normally - [MVMCoreLoadRequestOperation createViewControllerWithLoadObject:loadObject completionHandler:completionHandler]; + [MVMCoreDispatchUtility performBlockOnMainThread:^{ + if ([loadObject.operation checkIfNewControllerIsNeededWithLoadObject:loadObject]) { + [MVMCoreDispatchUtility performBlockInBackground:^{ + [MVMCoreLoadRequestOperation createViewControllerWithLoadObject:loadObject completionHandler:completionHandler]; + }]; + } + }]; } else { // Get the proper native error screen from the delegate [MVMCoreDispatchUtility performBlockOnMainThread:^{ diff --git a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache+Extension.swift b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache+Extension.swift new file mode 100644 index 0000000..ac03a82 --- /dev/null +++ b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache+Extension.swift @@ -0,0 +1,101 @@ +// +// Cache.swift +// JSONCreator +// +// Created by Matt Bruce on 3/20/24. +// Copyright © 2024 Verizon Wireless. All rights reserved. +// +import Foundation + +public enum CacheError: Error { + case serializationFailed + case deserializationFailed + case dataNotFound + case dataExpired + case saveFailed(Error) + case loadFailed(Error) +} + +public class CachedData: Codable { + public var data: [String: AnyHashable] + public var expirationDate: Date + enum CodingKeys: CodingKey { + case data, expirationDate + } + + public init(data: [String: AnyHashable], expirationDate: Date) { + self.data = data + self.expirationDate = expirationDate + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(expirationDate, forKey: .expirationDate) + let dataAsData = try JSONSerialization.data(withJSONObject: data, options: []) + try container.encode(dataAsData, forKey: .data) + } + + required public init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + expirationDate = try values.decode(Date.self, forKey: .expirationDate) + let dataAsData = try values.decode(Data.self, forKey: .data) + guard let jsonData = try JSONSerialization.jsonObject(with: dataAsData, options: []) as? [String: AnyHashable] else { + throw CacheError.deserializationFailed + } + data = jsonData + } +} + +@objc public class DataCacheManager: NSObject { + @objc public static let shared = DataCacheManager() +// private let cache = NSCache() + private let fileManager = FileManager.default + private lazy var documentsDirectory = { fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! }() + + public override init() {} + + @objc public func save(data: [String: AnyHashable], forKey key: String, cacheDuration: TimeInterval, shouldPersist: Bool = true) throws { + let expirationDate = Date().addingTimeInterval(cacheDuration) + let cachedData = CachedData(data: data, expirationDate: expirationDate) +// cache.setObject(cachedData, forKey: NSString(string: key)) + if shouldPersist { + let filePath = self.filePath(forKey: key) + do { + let dataToSave = try JSONEncoder().encode(cachedData) + try dataToSave.write(to: filePath, options: .atomicWrite) + } catch let error as EncodingError { + throw CacheError.serializationFailed + } catch { + throw CacheError.saveFailed(error) + } + } + } + + @objc public func load(forKey key: String) throws -> [String: AnyHashable] { + let keyNSString = NSString(string: key) +// if let cachedObject = cache.object(forKey: keyNSString), +// Date() < cachedObject.expirationDate { +// return cachedObject.data +// } else { + let filePath = self.filePath(forKey: key) + do { + let data = try Data(contentsOf: filePath) + let decodedCachedData = try JSONDecoder().decode(CachedData.self, from: data) + if Date() < decodedCachedData.expirationDate { +// cache.setObject(decodedCachedData, forKey: keyNSString) + return decodedCachedData.data + } else { + throw CacheError.dataExpired + } + } catch let error as DecodingError { + throw CacheError.deserializationFailed + } catch { + throw CacheError.loadFailed(error) + } +// } + } + + 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 ca15d8d..66686b4 100644 --- a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.h +++ b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.h @@ -36,6 +36,8 @@ typedef void(^MVMCoreGetImageBlock)(UIImage * _Nullable, NSData * _Nullable, BOO // Checks the set of modules to be cached for the given module - (BOOL)shouldCacheJSONWithModule:(nonnull NSString *)module; +- (BOOL)shouldPersistentlyCache:(nonnull NSDictionary *)jsonDictionary identifier:(nonnull NSString *)identifier; + // For pages external to the mobile first framework to be added to the list to not cache - (void)addPageTypesToNotCache:(nullable NSArray *)array; diff --git a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m index 8c43b9e..aef62c8 100644 --- a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m +++ b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m @@ -39,6 +39,8 @@ @property (nullable, strong, nonatomic) NSCache *playerItemCache; +@property (nullable, strong, nonatomic) NSSet *itemsToPersist; + @end @implementation MVMCoreCache @@ -65,6 +67,8 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; self.videoQueue = dispatch_queue_create("video_queue", dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0)); self.playerItemCache = [[NSCache alloc] init]; + + self.itemsToPersist = [NSSet setWithObjects:@"myFeed",@"FeedOrder",@"HabContent",@"launchModule",@"tabBar",@"DeepLinkService", nil]; } return self; } @@ -140,6 +144,10 @@ 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]; + } } else { // If no pagetype, return whole cache @@ -176,6 +184,12 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; NSDictionary *moduleDictionary = [weakSelf.moduleCache objectForKey:module]; if (moduleDictionary) { [modulesDictionary setObject:moduleDictionary forKey:module]; + } else { + NSError *error = nil; + moduleDictionary = [[DataCacheManager shared] loadForKey:module error:&error]; + if (moduleDictionary) { + [modulesDictionary setObject:moduleDictionary forKey:module]; + } } } @@ -206,6 +220,13 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; [self addModulesToCache:jsonDictionary queue:nil waitUntilFinished:NO completionBlock:NULL]; } +- (BOOL)shouldPersistentlyCache:(nonnull NSDictionary *)jsonDictionary identifier:(nonnull NSString *)identifier { + if ([self.itemsToPersist containsObject:identifier]) { + return YES; + } + return NO; +} + #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 { @@ -230,6 +251,10 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; NSNumber *shouldCache = [jsonDictionary optionalNumberForKey:@"cache"]; if (shouldCache == nil || shouldCache.boolValue) { [weakSelf.pageTypeCache setObject:jsonDictionary forKey:pageType]; + if ([self shouldPersistentlyCache:jsonDictionary identifier:pageType]) { + NSError *error = nil; + [[DataCacheManager shared] saveWithData:jsonDictionary forKey:pageType cacheDuration:604800 shouldPersist:YES error:&error]; + } } } } @@ -267,6 +292,11 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; NSNumber *shouldCache = [jsonDictionary optionalNumberForKey:@"cache"]; if (shouldCache == nil || shouldCache.boolValue) { [weakSelf.moduleCache setObject:obj forKey:key]; + + if ([self shouldPersistentlyCache:obj identifier:key]) { + NSError *error = nil; + [[DataCacheManager shared] saveWithData:obj forKey:key cacheDuration:604800 shouldPersist:YES error:&error]; + } } } }]; From 84f4a1ac46f7d3e744e6003eb73996309eb8cb8a Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Thu, 21 Mar 2024 10:39:25 -0400 Subject: [PATCH 02/15] Digital PCT265 story MVAPCT-48 - code fixes for cache optimizations --- .../MVMCore/LoadHandling/MVMCoreLoadHandler.m | 18 ++++++++++++++++-- ...MVMCoreLoadRequestOperation+Extension.swift | 10 +++++++--- .../LoadHandling/MVMCoreLoadRequestOperation.m | 4 ++-- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m index af912a4..940084f 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m @@ -383,13 +383,27 @@ if (requestParameters.backgroundRequest) { return [self loadBackgroundRequest:requestParameters dataForPage:dataForPage delegateObject:delegateObject]; } else { - if (!requestParameters.noloadingOverlay) { + BOOL loadingOverlay = NO; + if (!requestParameters.noloadingOverlay && [self.blockingLoadQueue.operations indexOfObjectPassingTest:^BOOL(__kindof NSOperation * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + if (!obj.isExecuting) { + [MVMCoreLoggingHandler logDebugMessageWithDelegate:[NSString stringWithFormat:@"CACHEDFEED: T No executor"]]; + return NO; } + if (![obj isKindOfClass:[MVMCoreLoadRequestOperation class]]) { + [MVMCoreLoggingHandler logDebugMessageWithDelegate:[NSString stringWithFormat:@"CACHEDFEED: T Not load request"]]; + return NO; } + [MVMCoreLoggingHandler logDebugMessageWithDelegate:[NSString stringWithFormat:@"CACHEDFEED: T %@ %i",((MVMCoreLoadRequestOperation *)obj).loadObject.requestParameters.pageType,((MVMCoreLoadRequestOperation *)obj).loadObject.requestParameters.noloadingOverlay]]; + return ((MVMCoreLoadRequestOperation *)obj).requestParameters.noloadingOverlay; + }] != NSNotFound) { + loadingOverlay = YES; + [MVMCoreLoggingHandler logDebugMessageWithDelegate:[NSString stringWithFormat:@"CACHEDFEED: We need a loading indicator"]]; [[MVMCoreLoadingOverlayHandler sharedLoadingOverlay] startLoading]; } MVMCoreLoadRequestOperation *loadOperation = [[MVMCoreLoadRequestOperation alloc] initWithRequestParameters:requestParameters dataForPage:dataForPage delegateObject:delegateObject backgroundLoad:NO]; loadOperation.identifier = requestParameters.identifier; [loadOperation setCompletionBlock:^{ - [[MVMCoreLoadingOverlayHandler sharedLoadingOverlay] stopLoading:YES]; + if (loadingOverlay) { + [[MVMCoreLoadingOverlayHandler sharedLoadingOverlay] stopLoading:YES]; + } }]; [self.blockingLoadQueue addOperation:loadOperation]; return loadOperation; diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation+Extension.swift b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation+Extension.swift index d3b4ea1..07bb9a5 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation+Extension.swift +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation+Extension.swift @@ -47,10 +47,14 @@ public extension MVMCoreLoadRequestOperation { MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: Controller shouldn't be replaced.") return true } - let newVC = MVMCoreViewControllerMappingObject.shared()?.createMFViewController(ofTemplate: loadObject.pageJSON?.optionalStringForKey("template"), pageType: pageType) + guard let newVC = MVMCoreViewControllerMappingObject.shared()?.createMFViewController(ofTemplate: loadObject.pageJSON?.optionalStringForKey("template"), pageType: pageType) else { + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: Failed to create a new controller") + return true + } guard let index = NavigationHandler.shared().navigationController?.viewControllers.firstIndex(where: { controller in - (controller as? MVMCoreViewControllerProtocol)?.pageType == pageType && type(of: controller) == type(of: newVC) - }) else { + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: Compare controller pageType:\(pageType) controllerPageType:\((controller as? MVMCoreViewControllerProtocol)?.pageType) type:\(type(of: controller)) newType:\(type(of: newVC))") + return (controller as? MVMCoreViewControllerProtocol)?.pageType == pageType && type(of: controller) == type(of: newVC) + }) else { MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: No matching controller found.") return true } diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m index 3517361..79ee8b2 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m @@ -132,7 +132,7 @@ } else { // No provided load object, check the cache for data first.. - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ +// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ [MVMCoreLoadRequestOperation checkCacheForDataForRequest:self.requestParameters completionHandler:^(NSDictionary *pageFromCache, NSDictionary *modulesFromCache) { @@ -199,7 +199,7 @@ }]; } }]; - }); +// }); } } From a16e09c569693d55b9f841e0a76acd20f81774c3 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Fri, 22 Mar 2024 11:02:04 -0400 Subject: [PATCH 03/15] Digital PCT265 story MVAPCT-48 - Cache piping improvements. --- MVMCore/MVMCore/OtherHandlers/MVMCoreCache.h | 9 ++++- MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m | 37 +++++++++++++------- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.h b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.h index 66686b4..3d0365d 100644 --- a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.h +++ b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.h @@ -36,7 +36,14 @@ typedef void(^MVMCoreGetImageBlock)(UIImage * _Nullable, NSData * _Nullable, BOO // Checks the set of modules to be cached for the given module - (BOOL)shouldCacheJSONWithModule:(nonnull NSString *)module; -- (BOOL)shouldPersistentlyCache:(nonnull NSDictionary *)jsonDictionary identifier:(nonnull NSString *)identifier; +/// Returns if the json is expired or not. +- (BOOL)isJSONExpired:(nonnull NSDictionary *)jsonDictionary; + +/// Checks if the page is to be persistently cached. +- (BOOL)shouldPersistentlyCachePage:(nonnull NSDictionary *)jsonDictionary pageType:(nonnull NSString *)pageType; + +/// Checks if the module is to be persistently cached. +- (BOOL)shouldPersistentlyCacheModule:(nonnull NSDictionary *)jsonDictionary module:(nonnull NSString *)module; // For pages external to the mobile first framework to be added to the list to not cache - (void)addPageTypesToNotCache:(nullable NSArray *)array; diff --git a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m index aef62c8..8fd3a82 100644 --- a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m +++ b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m @@ -39,8 +39,6 @@ @property (nullable, strong, nonatomic) NSCache *playerItemCache; -@property (nullable, strong, nonatomic) NSSet *itemsToPersist; - @end @implementation MVMCoreCache @@ -67,8 +65,6 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; self.videoQueue = dispatch_queue_create("video_queue", dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0)); self.playerItemCache = [[NSCache alloc] init]; - - self.itemsToPersist = [NSSet setWithObjects:@"myFeed",@"FeedOrder",@"HabContent",@"launchModule",@"tabBar",@"DeepLinkService", nil]; } return self; } @@ -103,6 +99,28 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; return ![self.modulesToNotCache containsObject:module]; } +- (BOOL)isJSONExpired:(nonnull NSDictionary *)jsonDictionary { + double expiryTime = [[jsonDictionary dict:@"expiry"] doubleValue] * 1000; + NSTimeInterval timeSinceUnixEpoc = [[NSDate date] timeIntervalSince1970]; + return timeSinceUnixEpoc > expiryTime; +} + +- (BOOL)shouldPersistentlyCacheJSON:(nonnull NSDictionary *)jsonDictionary { + NSDictionary *cachePolicy = [jsonDictionary dict:@"cachePolicy"]; + if (!cachePolicy || ![cachePolicy boolForKey:@"persist"]) { + return NO; + } + return [self isJSONExpired:jsonDictionary]; +} + +- (BOOL)shouldPersistentlyCachePage:(nonnull NSDictionary *)jsonDictionary pageType:(nonnull NSString *)pageType { + return [self shouldPersistentlyCacheJSON:jsonDictionary]; +} + +- (BOOL)shouldPersistentlyCacheModule:(nonnull NSDictionary *)jsonDictionary module:(nonnull NSString *)module { + return [self shouldPersistentlyCacheJSON:jsonDictionary]; +} + - (void)addPageTypesToNotCache:(nullable NSArray *)array { if (array) { [self.pageTypesToNotCache addObjectsFromArray:array]; @@ -220,13 +238,6 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; [self addModulesToCache:jsonDictionary queue:nil waitUntilFinished:NO completionBlock:NULL]; } -- (BOOL)shouldPersistentlyCache:(nonnull NSDictionary *)jsonDictionary identifier:(nonnull NSString *)identifier { - if ([self.itemsToPersist containsObject:identifier]) { - return YES; - } - return NO; -} - #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 { @@ -251,7 +262,7 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; NSNumber *shouldCache = [jsonDictionary optionalNumberForKey:@"cache"]; if (shouldCache == nil || shouldCache.boolValue) { [weakSelf.pageTypeCache setObject:jsonDictionary forKey:pageType]; - if ([self shouldPersistentlyCache:jsonDictionary identifier:pageType]) { + if ([self shouldPersistentlyCachePage:jsonDictionary pageType:pageType]) { NSError *error = nil; [[DataCacheManager shared] saveWithData:jsonDictionary forKey:pageType cacheDuration:604800 shouldPersist:YES error:&error]; } @@ -293,7 +304,7 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; if (shouldCache == nil || shouldCache.boolValue) { [weakSelf.moduleCache setObject:obj forKey:key]; - if ([self shouldPersistentlyCache:obj identifier:key]) { + if ([self shouldPersistentlyCacheModule:obj module:key]) { NSError *error = nil; [[DataCacheManager shared] saveWithData:obj forKey:key cacheDuration:604800 shouldPersist:YES error:&error]; } From b9097361ab61584ea7924f3888c05e1678146af3 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Fri, 22 Mar 2024 14:50:39 -0400 Subject: [PATCH 04/15] 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) { From 54e2ecb3134b08ea9d1c002cea2bef95e7033dee Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Mon, 25 Mar 2024 13:29:29 -0400 Subject: [PATCH 05/15] Manager updates for navigating to controller. --- ...VMCoreLoadRequestOperation+Extension.swift | 56 +++++++++++-------- .../MVMCoreLoadRequestOperation.m | 10 +++- .../MVMCoreViewManagerProtocol.h | 3 + .../NavigationHandler.swift | 7 +++ .../MVMCore/Singletons/MVMCoreObject.swift | 3 + 5 files changed, 53 insertions(+), 26 deletions(-) diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation+Extension.swift b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation+Extension.swift index 07bb9a5..a663b94 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation+Extension.swift +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation+Extension.swift @@ -41,37 +41,47 @@ public extension MVMCoreLoadRequestOperation { @objc @MainActor - func checkIfNewControllerIsNeeded(loadObject: MVMCoreLoadObject) -> Bool { + func goToViewController(loadObject: MVMCoreLoadObject) async -> UIViewController? { guard loadObject.requestParameters?.replaceViewIfOnStackElseLoadWithStyle == true, let pageType = loadObject.pageType else { MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: Controller shouldn't be replaced.") - return true + return nil } guard let newVC = MVMCoreViewControllerMappingObject.shared()?.createMFViewController(ofTemplate: loadObject.pageJSON?.optionalStringForKey("template"), pageType: pageType) else { MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: Failed to create a new controller") - return true + return nil } - guard let index = NavigationHandler.shared().navigationController?.viewControllers.firstIndex(where: { controller in - MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: Compare controller pageType:\(pageType) controllerPageType:\((controller as? MVMCoreViewControllerProtocol)?.pageType) type:\(type(of: controller)) newType:\(type(of: newVC))") - return (controller as? MVMCoreViewControllerProtocol)?.pageType == pageType && type(of: controller) == type(of: newVC) - }) else { - MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: No matching controller found.") - return true + let type = type(of: newVC) + guard let viewController = await NavigationHandler.shared().navigateToViewController(of: pageType, controllerType: type) else { + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: No matching controller found for \(pageType) \(type).") + return nil } - if index == NavigationHandler.shared().navigationController!.viewControllers.count - 1 { - MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: Controller is already showing.") - Task { - MVMCoreLoadRequestOperation.loadFinished(loadObject, loadedViewController: nil, errorObject: nil) - } - } else { - MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: Pop back to controller.") - guard let operation = try? NavigationHandler.shared().getOperationPopToViewController(with: pageType, navigationController: loadObject.requestParameters?.navigationController, delegateObject: loadObject.delegateObject, animated: !(loadObject.requestParameters?.shouldNotAnimatePush ?? false)) else { return true } - Task { - await navigate(with: operation, loadObject: loadObject) - MVMCoreLoadRequestOperation.loadFinished(loadObject, loadedViewController: nil, errorObject: nil) - } - } - return false + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: Navigated to controller of \(pageType) \(type).") + return viewController +// MVMCoreLoadRequestOperation.loadFinished(loadObject, loadedViewController: nil, errorObject: nil) +// +// +// guard let index = NavigationHandler.shared().navigationController?.viewControllers.firstIndex(where: { controller in +// MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: Compare controller pageType:\(pageType) controllerPageType:\((controller as? MVMCoreViewControllerProtocol)?.pageType) type:\(type(of: controller)) newType:\(type(of: newVC))") +// return (controller as? MVMCoreViewControllerProtocol)?.pageType == pageType && type(of: controller) == type(of: newVC) +// }) else { +// MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: No matching controller found.") +// return true +// } +// if index == NavigationHandler.shared().navigationController!.viewControllers.count - 1 { +// MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: Controller is already showing.") +// Task { +// MVMCoreLoadRequestOperation.loadFinished(loadObject, loadedViewController: nil, errorObject: nil) +// } +// } else { +// MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: Pop back to controller.") +// guard let operation = try? NavigationHandler.shared().getOperationPopToViewController(with: pageType, navigationController: loadObject.requestParameters?.navigationController, delegateObject: loadObject.delegateObject, animated: !(loadObject.requestParameters?.shouldNotAnimatePush ?? false)) else { return true } +// Task { +// await navigate(with: operation, loadObject: loadObject) +// MVMCoreLoadRequestOperation.loadFinished(loadObject, loadedViewController: nil, errorObject: nil) +// } +// } +// return false } @objc diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m index 79ee8b2..7a7d0a9 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m @@ -586,11 +586,15 @@ if (!error.nativeDrivenErrorScreen) { // Server driven screen, create normally [MVMCoreDispatchUtility performBlockOnMainThread:^{ - if ([loadObject.operation checkIfNewControllerIsNeededWithLoadObject:loadObject]) { + [loadObject.operation goToViewControllerWithLoadObject:loadObject completionHandler:^(UIViewController * _Nullable viewController) { [MVMCoreDispatchUtility performBlockInBackground:^{ - [MVMCoreLoadRequestOperation createViewControllerWithLoadObject:loadObject completionHandler:completionHandler]; + if (viewController) { + [MVMCoreLoadRequestOperation loadFinished:loadObject loadedViewController:viewController errorObject:nil]; + } else { + [MVMCoreLoadRequestOperation createViewControllerWithLoadObject:loadObject completionHandler:completionHandler]; + } }]; - } + }]; }]; } else { // Get the proper native error screen from the delegate diff --git a/MVMCore/MVMCore/MainProtocols/MVMCoreViewManagerProtocol.h b/MVMCore/MVMCore/MainProtocols/MVMCoreViewManagerProtocol.h index fda6bee..41438be 100644 --- a/MVMCore/MVMCore/MainProtocols/MVMCoreViewManagerProtocol.h +++ b/MVMCore/MVMCore/MainProtocols/MVMCoreViewManagerProtocol.h @@ -19,6 +19,9 @@ - (nullable NSArray*)getAccessibilityElements; //AccessibilityElements that are owned by Manager. +/// Attempt to navigate to the controller. Return true if navigation occured. +- (void)navigateToViewControllerOfPageType:(nonnull NSString *)pageType controllerType:(_Nullable Class)controllerType completionHandler:(void (^ __nullable)(UIViewController * _Nullable viewController))completionHandler; + @optional /// Notifies the manager that the controller received new data. diff --git a/MVMCore/MVMCore/PresentationHandling/NavigationHandler.swift b/MVMCore/MVMCore/PresentationHandling/NavigationHandler.swift index 2dfde17..ec27379 100644 --- a/MVMCore/MVMCore/PresentationHandling/NavigationHandler.swift +++ b/MVMCore/MVMCore/PresentationHandling/NavigationHandler.swift @@ -189,6 +189,13 @@ public class NavigationHandler { await navigate(with: .pop(navigationController: navigationController, animated: animated), delegateObject: delegateObject) } } + + /// Attempts to go to navigate to a viewcontroller of pageType and controllerType. Returns the view controller if successful + @MainActor + func navigateToViewController(of pageType: String, controllerType: AnyClass?) async -> UIViewController? { + // TODO: Need to manage for present view controllers. + return await MVMCoreObject.sharedInstance()?.viewControllerManager?.navigate(toViewControllerOfPageType: pageType, controllerType: controllerType) + } } extension UINavigationController { diff --git a/MVMCore/MVMCore/Singletons/MVMCoreObject.swift b/MVMCore/MVMCore/Singletons/MVMCoreObject.swift index 2f3fe67..27a14b6 100644 --- a/MVMCore/MVMCore/Singletons/MVMCoreObject.swift +++ b/MVMCore/MVMCore/Singletons/MVMCoreObject.swift @@ -23,6 +23,9 @@ public class MVMCoreObject: NSObject { public var globalLoadDelegate: MVMCoreGlobalLoadProtocol? public var loadingProtocol: MVMCoreLoadingOverlayDelegateProtocol? public var loggingDelegate: MVMCoreLoggingDelegateProtocol? + + /// The main manager of the view controllers in the application. + public weak var viewControllerManager: MVMCoreViewManagerProtocol? /// A reference to the calling application delegate that should be set. For a normal app, could be the UIApplicationDelegate. For watch, could be WKExtensionDelegate. For iMessage, could be MSMessagesAppViewController. etc, etc. Useful for the framework to call delegate specific functions. public weak var applicationDelegate: AnyObject? From f89bad1c7a3515588931c578597a3d1cf37a6cc7 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Tue, 26 Mar 2024 11:07:10 -0400 Subject: [PATCH 06/15] Digital PCT265 story MVAPCT-48 - Loading overlay cleanup --- .../MVMCore/LoadHandling/MVMCoreLoadHandler.m | 23 +++-------------- ...VMCoreLoadRequestOperation+Extension.swift | 8 +++--- .../MVMCoreLoadRequestOperation.h | 6 +++++ .../MVMCoreLoadRequestOperation.m | 25 ++++++++++++++----- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m index 940084f..3446798 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m @@ -383,28 +383,9 @@ if (requestParameters.backgroundRequest) { return [self loadBackgroundRequest:requestParameters dataForPage:dataForPage delegateObject:delegateObject]; } else { - BOOL loadingOverlay = NO; - if (!requestParameters.noloadingOverlay && [self.blockingLoadQueue.operations indexOfObjectPassingTest:^BOOL(__kindof NSOperation * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - if (!obj.isExecuting) { - [MVMCoreLoggingHandler logDebugMessageWithDelegate:[NSString stringWithFormat:@"CACHEDFEED: T No executor"]]; - return NO; } - if (![obj isKindOfClass:[MVMCoreLoadRequestOperation class]]) { - [MVMCoreLoggingHandler logDebugMessageWithDelegate:[NSString stringWithFormat:@"CACHEDFEED: T Not load request"]]; - return NO; } - [MVMCoreLoggingHandler logDebugMessageWithDelegate:[NSString stringWithFormat:@"CACHEDFEED: T %@ %i",((MVMCoreLoadRequestOperation *)obj).loadObject.requestParameters.pageType,((MVMCoreLoadRequestOperation *)obj).loadObject.requestParameters.noloadingOverlay]]; - return ((MVMCoreLoadRequestOperation *)obj).requestParameters.noloadingOverlay; - }] != NSNotFound) { - loadingOverlay = YES; - [MVMCoreLoggingHandler logDebugMessageWithDelegate:[NSString stringWithFormat:@"CACHEDFEED: We need a loading indicator"]]; - [[MVMCoreLoadingOverlayHandler sharedLoadingOverlay] startLoading]; - } MVMCoreLoadRequestOperation *loadOperation = [[MVMCoreLoadRequestOperation alloc] initWithRequestParameters:requestParameters dataForPage:dataForPage delegateObject:delegateObject backgroundLoad:NO]; loadOperation.identifier = requestParameters.identifier; - [loadOperation setCompletionBlock:^{ - if (loadingOverlay) { - [[MVMCoreLoadingOverlayHandler sharedLoadingOverlay] stopLoading:YES]; - } - }]; + [loadOperation startLoadingAnimationIfNeeded]; [self.blockingLoadQueue addOperation:loadOperation]; return loadOperation; } @@ -419,6 +400,8 @@ - (MVMCoreLoadRequestOperation *)loadObject:(nonnull MVMCoreLoadObject *)loadObject { MVMCoreLoadRequestOperation *loadOperation = [[MVMCoreLoadRequestOperation alloc] initWithLoadObject:loadObject backgroundLoad:NO]; + loadOperation.identifier = loadObject.requestParameters.identifier; + [loadOperation startLoadingAnimationIfNeeded]; [self.blockingLoadQueue addOperation:loadOperation]; return loadOperation; } diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation+Extension.swift b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation+Extension.swift index a663b94..002e7d4 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation+Extension.swift +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation+Extension.swift @@ -118,11 +118,9 @@ public extension MVMCoreLoadRequestOperation { // stop any loading animation we may have started if we are about to display cancellable = NavigationHandler.shared().onNavigation .filter { $0.0 == .willNavigate } - .sink { (event, operation) in - if navigationOperation == operation, - !self.backgroundLoad, - !(loadObject?.requestParameters?.noloadingOverlay ?? false) { - MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.stopLoading(false) + .sink { [weak self] (event, operation) in + if navigationOperation == operation { + self?.stopLoadingAnimationIfNeeded() } } await NavigationHandler.shared().navigate(with: navigationOperation) diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.h b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.h index fe334b3..fccbe82 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.h +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.h @@ -42,6 +42,12 @@ // Initializes the operation with the load object, data for page, and mvm view controller to handle the loading with. Can be used for loading a screen without going to the cache or server. - (nullable instancetype)initWithLoadObject:(nullable MVMCoreLoadObject *)loadObject backgroundLoad:(BOOL)backgroundLoad; +/// Begins the loading animation if needed. +- (void)startLoadingAnimationIfNeeded; + +/// Ends the loading animation if needed. +- (void)stopLoadingAnimationIfNeeded; + /* Checks the cache for the data and calls the completion handler with any found data. * @param completionHandler The block that gets called with any fetched data. */ + (void)checkCacheForDataForRequest:(nonnull MVMCoreRequestParameters *)requestParameters completionHandler:(nonnull void (^)(NSDictionary * _Nullable pageFromCache, NSDictionary * _Nullable modulesFromCache))completionHandler; diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m index 7a7d0a9..75feac9 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m @@ -34,6 +34,8 @@ @property (nonatomic, readwrite) BOOL alertToShow; @property (strong, nonatomic, nullable) MVMCoreErrorObject *errorForAlertToShow; +@property (nonatomic, readwrite) BOOL loadingAnimationRunning; + @end @implementation MVMCoreLoadRequestOperation @@ -64,14 +66,13 @@ - (void)cancel { [super cancel]; [self.sessionTask cancel]; + [self stopLoadingAnimationIfNeeded]; } - (void)start { // Adds a loading overlay if necessary. - if (!self.backgroundLoad && !self.requestParameters.noloadingOverlay) { - [[MVMCoreLoadingOverlayHandler sharedLoadingOverlay] startLoading]; - } + [self startLoadingAnimationIfNeeded]; [super start]; } @@ -83,9 +84,7 @@ - (void)markAsFinished { // stop any loading animation we may have started - if (!self.backgroundLoad && !self.requestParameters.noloadingOverlay) { - [[MVMCoreLoadingOverlayHandler sharedLoadingOverlay] stopLoading:NO]; - } + [self stopLoadingAnimationIfNeeded]; MVMCoreLog(@"Load Operation finished for page type %@, background load %@", self.requestParameters.pageType, @(self.backgroundLoad)); [super markAsFinished]; @@ -205,6 +204,20 @@ #pragma mark - Load Functions +- (void)startLoadingAnimationIfNeeded { + if (self.loadingAnimationRunning) { return; } + if (self.backgroundLoad) { return; } + if (self.requestParameters.noloadingOverlay) { return; } + [[MVMCoreLoadingOverlayHandler sharedLoadingOverlay] startLoading]; + self.loadingAnimationRunning = YES; +} + +- (void)stopLoadingAnimationIfNeeded { + if (!self.loadingAnimationRunning) { return; } + [[MVMCoreLoadingOverlayHandler sharedLoadingOverlay] stopLoading:YES]; + self.loadingAnimationRunning = NO; +} + + (void)checkCacheForDataForRequest:(nonnull MVMCoreRequestParameters *)requestParameters completionHandler:(nonnull void (^)(NSDictionary * _Nullable pageFromCache, NSDictionary * _Nullable modulesFromCache))completionHandler { if (requestParameters.neverLoadFromCache) { From c90f26759981e3d00e8b74d6119102d2f6a8defd Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Wed, 27 Mar 2024 11:03:27 -0400 Subject: [PATCH 07/15] Digital PCT265 story MVAPCT-48 - Remove test code --- .../MVMCoreLoadRequestOperation.m | 105 +++++++++--------- 1 file changed, 51 insertions(+), 54 deletions(-) diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m index 75feac9..4d05abe 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m @@ -131,74 +131,71 @@ } else { // No provided load object, check the cache for data first.. -// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + [MVMCoreLoadRequestOperation checkCacheForDataForRequest:self.requestParameters completionHandler:^(NSDictionary *pageFromCache, NSDictionary *modulesFromCache) { - [MVMCoreLoadRequestOperation checkCacheForDataForRequest:self.requestParameters completionHandler:^(NSDictionary *pageFromCache, NSDictionary *modulesFromCache) { + if ([self checkAndHandleForCancellation]) { + return; + } + + // Log if loaded from cache. + if (pageFromCache) { + MVMCoreLog(@"loaded from cache page %@",[MVMCoreActionUtility formatDictionaryAsJSONString:pageFromCache]); + } + if (modulesFromCache) { + MVMCoreLog(@"loaded from cache modules %@",[MVMCoreActionUtility formatDictionaryAsJSONString:modulesFromCache]); + } + + // Create a load object from any data we fetched. + MVMCoreLoadObject *loadObject = [self createLoadObjectWithPageFromCache:pageFromCache modulesFromCache:modulesFromCache]; + + // Check if we need to go to server for missing data. + MVMCoreRequestParameters *requestForMissingData = [MVMCoreLoadRequestOperation createRequestForDataWithLoadObject:loadObject]; + if (!requestForMissingData) { - if ([self checkAndHandleForCancellation]) { - return; + [[MVMCoreLoggingHandler sharedLoggingHandler] logPageLoadCompleteFor:loadObject.requestParameters.pageType serverProcessingTime:@"0" requestURL:loadObject.requestParameters.URL.absoluteString requestUUID:loadObject.identifier isFromCache:loadObject.pageDataFromCache]; + + // We have all the needed data, continue with the load. + [MVMCoreLoadRequestOperation handleLoadObject:loadObject error:nil]; + } else { + + if(!self.backgroundLoad && loadObject.requestParameters.pageType) { + [[MVMCoreLoggingHandler sharedLoggingHandler] logPageLoadStartedFor:loadObject.requestParameters.pageType requestUUID:loadObject.identifier requestURL:loadObject.requestParameters.URL.absoluteString]; } - // Log if loaded from cache. - if (pageFromCache) { - MVMCoreLog(@"loaded from cache page %@",[MVMCoreActionUtility formatDictionaryAsJSONString:pageFromCache]); - } - if (modulesFromCache) { - MVMCoreLog(@"loaded from cache modules %@",[MVMCoreActionUtility formatDictionaryAsJSONString:modulesFromCache]); - } - - // Create a load object from any data we fetched. - MVMCoreLoadObject *loadObject = [self createLoadObjectWithPageFromCache:pageFromCache modulesFromCache:modulesFromCache]; - - // Check if we need to go to server for missing data. - MVMCoreRequestParameters *requestForMissingData = [MVMCoreLoadRequestOperation createRequestForDataWithLoadObject:loadObject]; - if (!requestForMissingData) { + // Send a new request to the server. + [MVMCoreLoadRequestOperation sendRequest:requestForMissingData loadObject:loadObject completionHandler:^(NSDictionary * _Nullable json) { - [[MVMCoreLoggingHandler sharedLoggingHandler] logPageLoadCompleteFor:loadObject.requestParameters.pageType serverProcessingTime:@"0" requestURL:loadObject.requestParameters.URL.absoluteString requestUUID:loadObject.identifier isFromCache:loadObject.pageDataFromCache]; +#if ENABLE_HARD_CODED_RESPONSE + if ([[MVMCoreObject sharedInstance].globalLoadDelegate respondsToSelector:@selector(modifyJSON:)]) { + json = [[MVMCoreObject sharedInstance].globalLoadDelegate modifyJSON:json]; + } +#endif - // We have all the needed data, continue with the load. - [MVMCoreLoadRequestOperation handleLoadObject:loadObject error:nil]; - } else { + NSString *serverProcessTime = [(NSDictionary *)json objectChainOfKeysOrIndexes:@[@"ResponseInfo", @"timeStamp"]] ?: @"0"; - if(!self.backgroundLoad && loadObject.requestParameters.pageType) { - [[MVMCoreLoggingHandler sharedLoggingHandler] logPageLoadStartedFor:loadObject.requestParameters.pageType requestUUID:loadObject.identifier requestURL:loadObject.requestParameters.URL.absoluteString]; + if(!self.backgroundLoad && loadObject.requestParameters.pageType && serverProcessTime) { + [[MVMCoreLoggingHandler sharedLoggingHandler] logPageLoadCompleteFor:loadObject.requestParameters.pageType serverProcessingTime:serverProcessTime requestURL:loadObject.requestParameters.URL.absoluteString requestUUID:loadObject.identifier isFromCache:loadObject.pageDataFromCache]; } - // Send a new request to the server. - [MVMCoreLoadRequestOperation sendRequest:requestForMissingData loadObject:loadObject completionHandler:^(NSDictionary * _Nullable json) { + // Process the data retrieved from the server. + [MVMCoreLoadRequestOperation processJSONFromServer:json loadObject:loadObject completionHandler:^(MVMCoreLoadObject * _Nonnull loadObject, MVMCoreErrorObject * _Nullable error) { -#if ENABLE_HARD_CODED_RESPONSE - if ([[MVMCoreObject sharedInstance].globalLoadDelegate respondsToSelector:@selector(modifyJSON:)]) { - json = [[MVMCoreObject sharedInstance].globalLoadDelegate modifyJSON:json]; - } -#endif - - NSString *serverProcessTime = [(NSDictionary *)json objectChainOfKeysOrIndexes:@[@"ResponseInfo", @"timeStamp"]] ?: @"0"; - - if(!self.backgroundLoad && loadObject.requestParameters.pageType && serverProcessTime) { - [[MVMCoreLoggingHandler sharedLoggingHandler] logPageLoadCompleteFor:loadObject.requestParameters.pageType serverProcessingTime:serverProcessTime requestURL:loadObject.requestParameters.URL.absoluteString requestUUID:loadObject.identifier isFromCache:loadObject.pageDataFromCache]; + if ([loadObject.operation checkAndHandleForCancellation]) { + return; } - // Process the data retrieved from the server. - [MVMCoreLoadRequestOperation processJSONFromServer:json loadObject:loadObject completionHandler:^(MVMCoreLoadObject * _Nonnull loadObject, MVMCoreErrorObject * _Nullable error) { + if (loadObject.pageDataFromCache || loadObject.pageType) { - if ([loadObject.operation checkAndHandleForCancellation]) { - return; - } - - if (loadObject.pageDataFromCache || loadObject.pageType) { - - // Can continue loading with the page. - [MVMCoreLoadRequestOperation handleLoadObject:loadObject error:error]; - } else { - // Something to show, or nothing was expected to show, can finish. - [MVMCoreLoadRequestOperation loadFinished:loadObject loadedViewController:nil errorObject:error]; - } - }]; + // Can continue loading with the page. + [MVMCoreLoadRequestOperation handleLoadObject:loadObject error:error]; + } else { + // Something to show, or nothing was expected to show, can finish. + [MVMCoreLoadRequestOperation loadFinished:loadObject loadedViewController:nil errorObject:error]; + } }]; - } - }]; -// }); + }]; + } + }]; } } From 3b410fb522d744a93c076b8d96e4b20007cf359b Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Wed, 27 Mar 2024 11:30:52 -0400 Subject: [PATCH 08/15] Digital PCT265 story MVAPCT-48 - Add logging and fix expiry bug --- MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m index b0fac90..a1223aa 100644 --- a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m +++ b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m @@ -106,11 +106,15 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; - (BOOL)isJSONExpired:(nonnull NSDictionary *)jsonDictionary { NSTimeInterval expiryTime = [self getExpiryForJSON:jsonDictionary]; NSTimeInterval timeSinceUnixEpoc = [[NSDate date] timeIntervalSince1970]; + if (timeSinceUnixEpoc > expiryTime) { + [MVMCoreLoggingHandler logDebugMessageWithDelegate:[NSString stringWithFormat:@"CACHEDFEED: EXPIRED %@ %@ %f %f",[jsonDictionary stringForKey:KeyPageType],[jsonDictionary stringForKey:@"moduleName"],timeSinceUnixEpoc,expiryTime]]; + } return timeSinceUnixEpoc > expiryTime; } - (NSTimeInterval)getExpiryForJSON:(nonnull NSDictionary *)jsonDictionary { - return [[jsonDictionary dict:@"expiry"] doubleValue] * 1000.0; + NSDictionary *cachePolicy = [jsonDictionary dict:@"cachePolicy"]; + return [[cachePolicy dict:@"expiry"] doubleValue] * 1000.0; } - (BOOL)shouldPersistentlyCacheJSON:(nonnull NSDictionary *)jsonDictionary { From 4bdd93dbe57a18eee70843d4c51c329e94060a7c Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Wed, 27 Mar 2024 13:38:13 -0400 Subject: [PATCH 09/15] Digital PCT265 story MVAPCT-48 - Minor cache code cleanup --- .../MVMCoreCache+Extension.swift | 100 ++++++++---------- MVMCore/MVMCore/OtherHandlers/MVMCoreCache.h | 7 +- MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m | 43 ++++---- 3 files changed, 73 insertions(+), 77 deletions(-) diff --git a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache+Extension.swift b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache+Extension.swift index 8658be3..ac5b0fb 100644 --- a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache+Extension.swift +++ b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache+Extension.swift @@ -46,60 +46,51 @@ public class CachedData: Codable { } } -@objc public class DataCacheManager: NSObject { - @objc public static let shared = DataCacheManager() -// private let cache = NSCache() - private let fileManager = FileManager.default - private lazy var documentsDirectory = { fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! }() +@objc public class PersistentCacheManager: NSObject { + @objc public static let shared = PersistentCacheManager() + private let fileManager = FileManager.default + private lazy var documentsDirectory = { fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! }() - public override init() {} + private override init() {} - @objc public func save(data: [String: AnyHashable], forKey key: String, cacheDuration: TimeInterval, shouldPersist: Bool = true) throws { - let expirationDate = Date().addingTimeInterval(cacheDuration) - let cachedData = CachedData(data: data, expirationDate: expirationDate) -// cache.setObject(cachedData, forKey: NSString(string: key)) - if shouldPersist { - let filePath = self.filePath(forKey: key) - do { - let dataToSave = try JSONEncoder().encode(cachedData) - try dataToSave.write(to: filePath, options: .atomicWrite) - } catch let error as EncodingError { - throw CacheError.serializationFailed - } catch { - throw CacheError.saveFailed(error) - } + @objc public func save(data: [String: AnyHashable], forKey key: String, expirationDate: Date) throws { + let cachedData = CachedData(data: data, expirationDate: expirationDate) + let filePath = self.filePath(forKey: key) + do { + let dataToSave = try JSONEncoder().encode(cachedData) + try dataToSave.write(to: filePath, options: .atomicWrite) + } catch let error as EncodingError { + throw CacheError.serializationFailed + } catch { + throw CacheError.saveFailed(error) + } } - } @objc public func load(forKey key: String) throws -> [String: AnyHashable] { - let keyNSString = NSString(string: key) -// if let cachedObject = cache.object(forKey: keyNSString), -// Date() < cachedObject.expirationDate { -// return cachedObject.data -// } else { - let filePath = self.filePath(forKey: key) - do { - let data = try Data(contentsOf: filePath) - let decodedCachedData = try JSONDecoder().decode(CachedData.self, from: data) - if Date() < decodedCachedData.expirationDate { -// cache.setObject(decodedCachedData, forKey: keyNSString) - return decodedCachedData.data - } else { - throw CacheError.dataExpired + let keyNSString = NSString(string: key) + let filePath = self.filePath(forKey: key) + do { + let data = try Data(contentsOf: filePath) + let decodedCachedData = try JSONDecoder().decode(CachedData.self, from: data) + if Date() < decodedCachedData.expirationDate { + return decodedCachedData.data + } else { + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: EXPIRED, key:\(key)") + throw CacheError.dataExpired + } + } catch { + // 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) + } } - } catch { - // 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) @@ -109,12 +100,13 @@ public class CachedData: Codable { 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) - } + + 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") - } + 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 945e22a..07ec07c 100644 --- a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.h +++ b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.h @@ -40,7 +40,7 @@ typedef void(^MVMCoreGetImageBlock)(UIImage * _Nullable, NSData * _Nullable, BOO - (BOOL)isJSONExpired:(nonnull NSDictionary *)jsonDictionary; /// Returns the expiry time for the object. -- (NSTimeInterval)getExpiryForJSON:(nonnull NSDictionary *)jsonDictionary; +- (nonnull NSDate *)getExpirationDateForJSON:(nonnull NSDictionary *)jsonDictionary; /// Checks if the page is to be persistently cached. - (BOOL)shouldPersistentlyCachePage:(nonnull NSDictionary *)jsonDictionary pageType:(nonnull NSString *)pageType; @@ -94,7 +94,10 @@ typedef void(^MVMCoreGetImageBlock)(UIImage * _Nullable, NSData * _Nullable, BOO - (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; +- (void)addPageToPersistentCache:(nonnull NSDictionary *)jsonDictionary pageType:(nonnull NSString *)pageType expirationDate:(nonnull NSDate *)expirationDate; + +/// Adds the json to the persistent cache by module. +- (void)addModuleToPersistentCache:(nonnull NSDictionary *)jsonDictionary moduleName:(nonnull NSString *)moduleName expirationDate:(nonnull NSDate *)expirationDate; #pragma mark - Advanced Insertion diff --git a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m index a1223aa..754f4a3 100644 --- a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m +++ b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m @@ -104,17 +104,18 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; } - (BOOL)isJSONExpired:(nonnull NSDictionary *)jsonDictionary { - NSTimeInterval expiryTime = [self getExpiryForJSON:jsonDictionary]; - NSTimeInterval timeSinceUnixEpoc = [[NSDate date] timeIntervalSince1970]; - if (timeSinceUnixEpoc > expiryTime) { - [MVMCoreLoggingHandler logDebugMessageWithDelegate:[NSString stringWithFormat:@"CACHEDFEED: EXPIRED %@ %@ %f %f",[jsonDictionary stringForKey:KeyPageType],[jsonDictionary stringForKey:@"moduleName"],timeSinceUnixEpoc,expiryTime]]; + NSDate *expirationDate = [self getExpirationDateForJSON:jsonDictionary]; + NSDate *today = [NSDate date]; + if (today > expirationDate) { + [MVMCoreLoggingHandler logDebugMessageWithDelegate:[NSString stringWithFormat:@"CACHEDFEED: NEW DATA ALREADY EXPIRED %@ %@ %@ %@",[jsonDictionary stringForKey:KeyPageType],[jsonDictionary stringForKey:@"moduleName"],today,expirationDate]]; } - return timeSinceUnixEpoc > expiryTime; + return today > expirationDate; } -- (NSTimeInterval)getExpiryForJSON:(nonnull NSDictionary *)jsonDictionary { +- (nonnull NSDate *)getExpirationDateForJSON:(nonnull NSDictionary *)jsonDictionary { NSDictionary *cachePolicy = [jsonDictionary dict:@"cachePolicy"]; - return [[cachePolicy dict:@"expiry"] doubleValue] * 1000.0; + NSTimeInterval interval = [[cachePolicy string:@"expiry"] doubleValue] / 1000; + return [NSDate dateWithTimeIntervalSince1970:interval]; } - (BOOL)shouldPersistentlyCacheJSON:(nonnull NSDictionary *)jsonDictionary { @@ -161,12 +162,12 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; - (NSDictionary * _Nullable)fetchPageFromPersistentCache:(nonnull NSString *)pageType { NSError *error = nil; - return [[DataCacheManager shared] loadForKey:pageType error:&error]; + return [[PersistentCacheManager shared] loadForKey:pageType error:&error]; } - (NSDictionary * _Nullable)fetchModuleFromPersistentCache:(nonnull NSString *)moduleName { NSError *error = nil; - return [[DataCacheManager shared] loadForKey:moduleName error:&error]; + return [[PersistentCacheManager shared] loadForKey:moduleName error:&error]; } #pragma mark - Advanced Fetch @@ -258,14 +259,14 @@ 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 { +- (void)addPageToPersistentCache:(nonnull NSDictionary *)jsonDictionary pageType:(nonnull NSString *)pageType expirationDate:(nonnull NSDate *)expirationDate { NSError *error = nil; - [[DataCacheManager shared] saveWithData:jsonDictionary forKey:pageType cacheDuration:expiry shouldPersist:YES error:&error]; + [[PersistentCacheManager shared] saveWithData:jsonDictionary forKey:pageType expirationDate:expirationDate error:&error]; } -- (void)addModuleToPersistentCache:(nonnull NSDictionary *)jsonDictionary moduleName:(nonnull NSString *)moduleName expiry:(NSTimeInterval)expiry { +- (void)addModuleToPersistentCache:(nonnull NSDictionary *)jsonDictionary moduleName:(nonnull NSString *)moduleName expirationDate:(nonnull NSDate *)expirationDate { NSError *error = nil; - [[DataCacheManager shared] saveWithData:jsonDictionary forKey:moduleName cacheDuration:expiry shouldPersist:YES error:&error]; + [[PersistentCacheManager shared] saveWithData:jsonDictionary forKey:moduleName expirationDate:expirationDate error:&error]; } #pragma mark - Advanced Insertion @@ -297,10 +298,10 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; [weakSelf.pageTypeCache setObject:jsonDictionary forKey:pageType]; if (![self shouldPersistentlyCachePage:jsonDictionary pageType:pageType]) { - [[DataCacheManager shared] removeForKey:pageType error:nil]; + [[PersistentCacheManager shared] removeForKey:pageType error:nil]; return; } - [self addPageToPersistentCache:jsonDictionary pageType:pageType expiry:[self getExpiryForJSON:jsonDictionary]]; + [self addPageToPersistentCache:jsonDictionary pageType:pageType expirationDate:[self getExpirationDateForJSON:jsonDictionary]]; } } if (completionBlock) { @@ -333,10 +334,10 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; [weakSelf.moduleCache setObject:jsonDictionary forKey:module]; if (![self shouldPersistentlyCacheModule:jsonDictionary module:module]) { - [[DataCacheManager shared] removeForKey:module error:nil]; + [[PersistentCacheManager shared] removeForKey:module error:nil]; return; } - [self addModuleToPersistentCache:jsonDictionary moduleName:module expiry:[self getExpiryForJSON:jsonDictionary]]; + [self addModuleToPersistentCache:jsonDictionary moduleName:module expirationDate:[self getExpirationDateForJSON:jsonDictionary]]; } if (completionBlock) { [(queue ?: weakSelf.completionQueue) addOperations:@[[NSBlockOperation blockOperationWithBlock:completionBlock]] waitUntilFinished:waitUntilFinished]; @@ -372,7 +373,7 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; } - (void)clearPersistentJSONCache { - [[DataCacheManager shared] removeAllAndReturnError:nil]; + [[PersistentCacheManager shared] removeAllAndReturnError:nil]; } - (void)clearMFCache { @@ -412,7 +413,7 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; } }]; [self.pageTypeQueue addOperations:@[removeOperation] waitUntilFinished:waitUntilFinished]; - [[DataCacheManager shared] removeForKey:pageType error:nil]; + [[PersistentCacheManager shared] removeForKey:pageType error:nil]; } - (void)removeJSONForModule:(nonnull NSString *)module queue:(nullable NSOperationQueue *)queue waitUntilFinished:(BOOL)waitUntilFinished completionBlock:(nullable void (^)(void))completionBlock { @@ -436,7 +437,7 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; } }]; [self.moduleQueue addOperations:@[removeOperation] waitUntilFinished:waitUntilFinished]; - [[DataCacheManager shared] removeForKey:module error:nil]; + [[PersistentCacheManager shared] removeForKey:module error:nil]; } - (void)removeJSONForModules:(nonnull NSArray *)modules queue:(nullable NSOperationQueue *)queue waitUntilFinished:(BOOL)waitUntilFinished completionBlock:(nullable void (^)(void))completionBlock { @@ -456,7 +457,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]; + [[PersistentCacheManager shared] removeForKey:obj error:nil]; } }]; if (completionBlock) { From 4fc4aa21f3414a9a5e44a7243c9aa506030ce8de Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Thu, 28 Mar 2024 13:35:07 -0400 Subject: [PATCH 10/15] Digital PCT265 story MVAPCT-48 - More expiry logic fixes --- .../MVMCore/OtherHandlers/MVMCoreCache+Extension.swift | 4 ++-- MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache+Extension.swift b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache+Extension.swift index ac5b0fb..ff7fda5 100644 --- a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache+Extension.swift +++ b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache+Extension.swift @@ -59,7 +59,7 @@ public class CachedData: Codable { do { let dataToSave = try JSONEncoder().encode(cachedData) try dataToSave.write(to: filePath, options: .atomicWrite) - } catch let error as EncodingError { + } catch is EncodingError { throw CacheError.serializationFailed } catch { throw CacheError.saveFailed(error) @@ -67,12 +67,12 @@ public class CachedData: Codable { } @objc public func load(forKey key: String) throws -> [String: AnyHashable] { - let keyNSString = NSString(string: key) let filePath = self.filePath(forKey: key) do { let data = try Data(contentsOf: filePath) let decodedCachedData = try JSONDecoder().decode(CachedData.self, from: data) if Date() < decodedCachedData.expirationDate { + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: LOADED FROM PERSISTENT CACHE, key:\(key)") return decodedCachedData.data } else { MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: EXPIRED, key:\(key)") diff --git a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m index 754f4a3..1a4aa98 100644 --- a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m +++ b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m @@ -105,11 +105,11 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; - (BOOL)isJSONExpired:(nonnull NSDictionary *)jsonDictionary { NSDate *expirationDate = [self getExpirationDateForJSON:jsonDictionary]; - NSDate *today = [NSDate date]; - if (today > expirationDate) { - [MVMCoreLoggingHandler logDebugMessageWithDelegate:[NSString stringWithFormat:@"CACHEDFEED: NEW DATA ALREADY EXPIRED %@ %@ %@ %@",[jsonDictionary stringForKey:KeyPageType],[jsonDictionary stringForKey:@"moduleName"],today,expirationDate]]; + NSDate *now = [NSDate date]; + if ([now compare:expirationDate] == NSOrderedDescending) { + [MVMCoreLoggingHandler logDebugMessageWithDelegate:[NSString stringWithFormat:@"CACHEDFEED: NEW DATA ALREADY EXPIRED %@ now:%@ expirationDate:%@",jsonDictionary,now,expirationDate]]; } - return today > expirationDate; + return [now compare:expirationDate] == NSOrderedDescending; } - (nonnull NSDate *)getExpirationDateForJSON:(nonnull NSDictionary *)jsonDictionary { @@ -123,7 +123,7 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; if (!cachePolicy || ![cachePolicy boolForKey:@"persist"]) { return NO; } - return [self isJSONExpired:jsonDictionary]; + return ![self isJSONExpired:jsonDictionary]; } - (BOOL)shouldPersistentlyCachePage:(nonnull NSDictionary *)jsonDictionary pageType:(nonnull NSString *)pageType { From d08a8f67822fe0b49ffabbedab04f77dc97efe7b Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Fri, 29 Mar 2024 16:45:23 -0400 Subject: [PATCH 11/15] Digital PCT265 story MVAPCT-48 - Caches root directory, file protection for encryption, atomicWrite API deprecation. --- .../OtherHandlers/MVMCoreCache+Extension.swift | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache+Extension.swift b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache+Extension.swift index ff7fda5..fdbff0b 100644 --- a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache+Extension.swift +++ b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache+Extension.swift @@ -49,7 +49,7 @@ public class CachedData: Codable { @objc public class PersistentCacheManager: NSObject { @objc public static let shared = PersistentCacheManager() private let fileManager = FileManager.default - private lazy var documentsDirectory = { fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! }() + private lazy var cacheDirectory = { fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!.appendingPathComponent("Atomic")}() private override init() {} @@ -57,8 +57,9 @@ public class CachedData: Codable { let cachedData = CachedData(data: data, expirationDate: expirationDate) let filePath = self.filePath(forKey: key) do { + try FileManager.default.createDirectory(atPath: self.cacheDirectory.relativePath, withIntermediateDirectories: true, attributes: [.protectionKey: FileProtectionType.complete]) let dataToSave = try JSONEncoder().encode(cachedData) - try dataToSave.write(to: filePath, options: .atomicWrite) + try dataToSave.write(to: filePath, options: [.atomic, .completeFileProtection]) } catch is EncodingError { throw CacheError.serializationFailed } catch { @@ -97,16 +98,18 @@ public class CachedData: Codable { } @objc public func removeAll() throws { - let fileURLs = try fileManager.contentsOfDirectory(at: documentsDirectory, - includingPropertiesForKeys: nil, - options: .skipsHiddenFiles) - + let fileURLs = try fileManager.contentsOfDirectory( + at: cacheDirectory, + 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") + return cacheDirectory.appendingPathComponent("\(key).json") } } From 8235aff75f7790754db6cba967d1c51527965455 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Mon, 1 Apr 2024 18:07:52 -0400 Subject: [PATCH 12/15] Digital PCT265 story MVAPCT-48 - Added cache timing hacks --- .../MVMCoreLoadRequestOperation.m | 4 +++ .../MainProtocols/MVMCoreGlobalLoadProtocol.h | 3 ++ .../MVMCoreCache+Extension.swift | 31 +++++++------------ MVMCore/MVMCore/OtherHandlers/MVMCoreCache.h | 6 ++++ MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m | 22 ++++++++++--- 5 files changed, 42 insertions(+), 24 deletions(-) diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m index 4d05abe..78108f9 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m @@ -443,6 +443,10 @@ NSDictionary *systemParameters = [jsonDictionary dict:KeySystemParameters]; loadObject.systemParametersJSON = systemParameters.count > 0 ? systemParameters : nil; + if ([[MVMCoreObject sharedInstance].globalLoadDelegate respondsToSelector:@selector(willProcessLoadObject:)]) { + [[MVMCoreObject sharedInstance].globalLoadDelegate willProcessLoadObject:loadObject]; + } + // module items are cached. MVMCoreErrorObject *moduleCachingError = nil; BOOL shouldContinue = [MVMCoreLoadRequestOperation cacheModules:modules loadObject:loadObject error:&moduleCachingError]; diff --git a/MVMCore/MVMCore/MainProtocols/MVMCoreGlobalLoadProtocol.h b/MVMCore/MVMCore/MainProtocols/MVMCoreGlobalLoadProtocol.h index 76fede6..93f1b23 100644 --- a/MVMCore/MVMCore/MainProtocols/MVMCoreGlobalLoadProtocol.h +++ b/MVMCore/MVMCore/MainProtocols/MVMCoreGlobalLoadProtocol.h @@ -41,6 +41,9 @@ /// Checks to see if the operation has content to show. - (BOOL)hasContentToShow:(nonnull MVMCoreLoadObject *)loadObject error:(nullable MVMCoreErrorObject *)error; +/// Notifies the delegate we are about to process the load object. +- (void)willProcessLoadObject:(nonnull MVMCoreLoadObject *)loadObject; + #if ENABLE_HARD_CODED_RESPONSE - (nullable NSDictionary *)getJSONForRequestParameters:(nonnull MVMCoreRequestParameters *)requestParameters; - (nonnull NSDictionary *)modifyJSON:(nonnull NSDictionary *)json; diff --git a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache+Extension.swift b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache+Extension.swift index fdbff0b..7210021 100644 --- a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache+Extension.swift +++ b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache+Extension.swift @@ -49,17 +49,17 @@ public class CachedData: Codable { @objc public class PersistentCacheManager: NSObject { @objc public static let shared = PersistentCacheManager() private let fileManager = FileManager.default - private lazy var cacheDirectory = { fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!.appendingPathComponent("Atomic")}() + @objc public lazy var cacheDirectory = { fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!.appendingPathComponent("Atomic")}() private override init() {} - @objc public func save(data: [String: AnyHashable], forKey key: String, expirationDate: Date) throws { + @objc public func save(data: [String: AnyHashable], forKey key: String, path: URL, expirationDate: Date) throws { let cachedData = CachedData(data: data, expirationDate: expirationDate) - let filePath = self.filePath(forKey: key) do { - try FileManager.default.createDirectory(atPath: self.cacheDirectory.relativePath, withIntermediateDirectories: true, attributes: [.protectionKey: FileProtectionType.complete]) + try FileManager.default.createDirectory(atPath: path.deletingLastPathComponent().relativePath, withIntermediateDirectories: true, attributes: [.protectionKey: FileProtectionType.complete]) let dataToSave = try JSONEncoder().encode(cachedData) - try dataToSave.write(to: filePath, options: [.atomic, .completeFileProtection]) + try dataToSave.write(to: path, options: [.atomic, .completeFileProtection]) + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: SAVED TO PERSISTENT CACHE, key:\(key), path:\(path)") } catch is EncodingError { throw CacheError.serializationFailed } catch { @@ -67,21 +67,20 @@ public class CachedData: Codable { } } - @objc public func load(forKey key: String) throws -> [String: AnyHashable] { - let filePath = self.filePath(forKey: key) + @objc public func load(forKey key: String, path: URL) throws -> [String: AnyHashable] { do { - let data = try Data(contentsOf: filePath) + let data = try Data(contentsOf: path) let decodedCachedData = try JSONDecoder().decode(CachedData.self, from: data) if Date() < decodedCachedData.expirationDate { - MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: LOADED FROM PERSISTENT CACHE, key:\(key)") + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: LOADED FROM PERSISTENT CACHE, key:\(key), path:\(path)") return decodedCachedData.data } else { - MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: EXPIRED, key:\(key)") + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: EXPIRED, key:\(key), path:\(path)") throw CacheError.dataExpired } } catch { // Remove item from the cache on any failure. - try fileManager.removeItem(at: filePath) + try fileManager.removeItem(at: path) switch error { case is DecodingError: @@ -98,15 +97,7 @@ public class CachedData: Codable { } @objc public func removeAll() throws { - let fileURLs = try fileManager.contentsOfDirectory( - at: cacheDirectory, - includingPropertiesForKeys: nil, - options: .skipsHiddenFiles - ) - - for fileURL in fileURLs where fileURL.pathExtension == "json" { - try FileManager.default.removeItem(at: fileURL) - } + try FileManager.default.removeItem(at: cacheDirectory) } private func filePath(forKey key: String) -> URL { diff --git a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.h b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.h index 07ec07c..6319fa5 100644 --- a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.h +++ b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.h @@ -48,6 +48,12 @@ typedef void(^MVMCoreGetImageBlock)(UIImage * _Nullable, NSData * _Nullable, BOO /// Checks if the module is to be persistently cached. - (BOOL)shouldPersistentlyCacheModule:(nonnull NSDictionary *)jsonDictionary module:(nonnull NSString *)module; +/// Can override the path for the page to be cached. Currently Cache/Atomic/Pages/pageType +- (nullable NSURL *)getPathForPersistentCachePage:(nonnull NSString *)pageType; + +/// Can override the path for the page to be cached. Currently Cache/Atomic/Modules/moduleName +- (nullable NSURL *)getPathForPersistentCacheModule:(nonnull NSString *)moduleName; + // For pages external to the mobile first framework to be added to the list to not cache - (void)addPageTypesToNotCache:(nullable NSArray *)array; diff --git a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m index 1a4aa98..3f1fa92 100644 --- a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m +++ b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m @@ -134,6 +134,14 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; return [self shouldPersistentlyCacheJSON:jsonDictionary]; } +- (nullable NSURL *)getPathForPersistentCachePage:(nonnull NSString *)pageType { + return [[[[PersistentCacheManager shared].cacheDirectory URLByAppendingPathComponent:@"Pages"] URLByAppendingPathComponent:pageType] URLByAppendingPathExtension:@"json"]; +} + +- (nullable NSURL *)getPathForPersistentCacheModule:(nonnull NSString *)moduleName { + return [[[[PersistentCacheManager shared].cacheDirectory URLByAppendingPathComponent:@"Modules"] URLByAppendingPathComponent:moduleName]URLByAppendingPathExtension:@"json"]; +} + - (void)addPageTypesToNotCache:(nullable NSArray *)array { if (array) { [self.pageTypesToNotCache addObjectsFromArray:array]; @@ -162,12 +170,12 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; - (NSDictionary * _Nullable)fetchPageFromPersistentCache:(nonnull NSString *)pageType { NSError *error = nil; - return [[PersistentCacheManager shared] loadForKey:pageType error:&error]; + return [[PersistentCacheManager shared] loadForKey:pageType path:[self getPathForPersistentCachePage:pageType] error:&error]; } - (NSDictionary * _Nullable)fetchModuleFromPersistentCache:(nonnull NSString *)moduleName { NSError *error = nil; - return [[PersistentCacheManager shared] loadForKey:moduleName error:&error]; + return [[PersistentCacheManager shared] loadForKey:moduleName path:[self getPathForPersistentCacheModule:moduleName] error:&error]; } #pragma mark - Advanced Fetch @@ -261,12 +269,18 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; - (void)addPageToPersistentCache:(nonnull NSDictionary *)jsonDictionary pageType:(nonnull NSString *)pageType expirationDate:(nonnull NSDate *)expirationDate { NSError *error = nil; - [[PersistentCacheManager shared] saveWithData:jsonDictionary forKey:pageType expirationDate:expirationDate error:&error]; + [[PersistentCacheManager shared] saveWithData:jsonDictionary forKey:pageType path:[self getPathForPersistentCachePage:pageType] expirationDate:expirationDate error:&error]; + if (error) { + [[MVMCoreLoggingHandler sharedLoggingHandler] addErrorToLog:[MVMCoreErrorObject createErrorObjectForNSError:error location:[NSString stringWithFormat:@"%s_%@",__PRETTY_FUNCTION__,pageType]]]; + } } - (void)addModuleToPersistentCache:(nonnull NSDictionary *)jsonDictionary moduleName:(nonnull NSString *)moduleName expirationDate:(nonnull NSDate *)expirationDate { NSError *error = nil; - [[PersistentCacheManager shared] saveWithData:jsonDictionary forKey:moduleName expirationDate:expirationDate error:&error]; + [[PersistentCacheManager shared] saveWithData:jsonDictionary forKey:moduleName path:[self getPathForPersistentCacheModule:moduleName] expirationDate:expirationDate error:&error]; + if (error) { + [[MVMCoreLoggingHandler sharedLoggingHandler] addErrorToLog:[MVMCoreErrorObject createErrorObjectForNSError:error location:[NSString stringWithFormat:@"%s_%@",__PRETTY_FUNCTION__,moduleName]]]; + } } #pragma mark - Advanced Insertion From 25b79530eb58017fa377f509313e82be676aed85 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Wed, 3 Apr 2024 12:20:24 -0400 Subject: [PATCH 13/15] Digital PCT265 story MVAPCT-48 - Code cleanup --- ...VMCoreLoadRequestOperation+Extension.swift | 56 +++++++------------ .../MVMCoreViewManagerProtocol.h | 2 +- .../MVMCoreViewControllerMappingObject.h | 3 + .../MVMCoreViewControllerMappingObject.m | 10 ++++ 4 files changed, 35 insertions(+), 36 deletions(-) diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation+Extension.swift b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation+Extension.swift index 002e7d4..468bf40 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation+Extension.swift +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation+Extension.swift @@ -39,49 +39,37 @@ public enum PopBackError: MVMError, CustomStringConvertible { @objc public extension MVMCoreLoadRequestOperation { + /// Attempt to navigate to the controller with the given load object. Return the controller that we navigated to if successful. @objc @MainActor func goToViewController(loadObject: MVMCoreLoadObject) async -> UIViewController? { guard loadObject.requestParameters?.replaceViewIfOnStackElseLoadWithStyle == true, let pageType = loadObject.pageType else { - MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: Controller shouldn't be replaced.") + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "\(type(of: self)): A new controller should be made and any existing shouldn't be replaced. pageType:\(String(describing: loadObject.pageType))") return nil } - guard let newVC = MVMCoreViewControllerMappingObject.shared()?.createMFViewController(ofTemplate: loadObject.pageJSON?.optionalStringForKey("template"), pageType: pageType) else { - MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: Failed to create a new controller") + let template = loadObject.pageJSON?.optionalStringForKey("template") + guard let controllerMappingObject = MVMCoreViewControllerMappingObject.shared()?.getViewControllerMapping(forTemplate: template, pageType: pageType) else { + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "\(type(of: self)): Failed to create a new controller. template:\(String(describing: template)) page:\(pageType)") return nil } - let type = type(of: newVC) - guard let viewController = await NavigationHandler.shared().navigateToViewController(of: pageType, controllerType: type) else { - MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: No matching controller found for \(pageType) \(type).") + var controllerType: AnyClass? + if let programmaticMapping = controllerMappingObject as? MVMCoreViewControllerProgrammaticMappingObject { + controllerType = programmaticMapping.viewControllerClass + } else if let newVC = MVMCoreViewControllerMappingObject.shared()?.createMFViewController(ofTemplate: loadObject.pageJSON?.optionalStringForKey("template"), pageType: pageType) { + // Need to create the view controller to fetch the type. + controllerType = type(of: newVC) + } else { + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "\(type(of: self)): Failed to create a new controller. template:\(String(describing: template)) page:\(pageType)") return nil } - MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: Navigated to controller of \(pageType) \(type).") + + guard let viewController = await NavigationHandler.shared().navigateToViewController(of: pageType, controllerType: controllerType) else { + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "\(type(of: self)): No matching controller found in the hierarchy. Will need to create a new controller. pageType:\(pageType) controllerType:\(String(describing: controllerType)).") + return nil + } + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "\(type(of: self)): Navigated to controller. pageType:\(pageType) controllerType:\(String(describing: controllerType))") return viewController -// MVMCoreLoadRequestOperation.loadFinished(loadObject, loadedViewController: nil, errorObject: nil) -// -// -// guard let index = NavigationHandler.shared().navigationController?.viewControllers.firstIndex(where: { controller in -// MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: Compare controller pageType:\(pageType) controllerPageType:\((controller as? MVMCoreViewControllerProtocol)?.pageType) type:\(type(of: controller)) newType:\(type(of: newVC))") -// return (controller as? MVMCoreViewControllerProtocol)?.pageType == pageType && type(of: controller) == type(of: newVC) -// }) else { -// MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: No matching controller found.") -// return true -// } -// if index == NavigationHandler.shared().navigationController!.viewControllers.count - 1 { -// MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: Controller is already showing.") -// Task { -// MVMCoreLoadRequestOperation.loadFinished(loadObject, loadedViewController: nil, errorObject: nil) -// } -// } else { -// MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: Pop back to controller.") -// guard let operation = try? NavigationHandler.shared().getOperationPopToViewController(with: pageType, navigationController: loadObject.requestParameters?.navigationController, delegateObject: loadObject.delegateObject, animated: !(loadObject.requestParameters?.shouldNotAnimatePush ?? false)) else { return true } -// Task { -// await navigate(with: operation, loadObject: loadObject) -// MVMCoreLoadRequestOperation.loadFinished(loadObject, loadedViewController: nil, errorObject: nil) -// } -// } -// return false } @objc @@ -117,11 +105,9 @@ public extension MVMCoreLoadRequestOperation { func navigate(with navigationOperation: NavigationOperation, loadObject: MVMCoreLoadObject?) async { // stop any loading animation we may have started if we are about to display cancellable = NavigationHandler.shared().onNavigation - .filter { $0.0 == .willNavigate } + .filter { $0.0 == .willNavigate && navigationOperation == $0.1 } .sink { [weak self] (event, operation) in - if navigationOperation == operation { - self?.stopLoadingAnimationIfNeeded() - } + self?.stopLoadingAnimationIfNeeded() } await NavigationHandler.shared().navigate(with: navigationOperation) cancellable = nil diff --git a/MVMCore/MVMCore/MainProtocols/MVMCoreViewManagerProtocol.h b/MVMCore/MVMCore/MainProtocols/MVMCoreViewManagerProtocol.h index 41438be..6875bb0 100644 --- a/MVMCore/MVMCore/MainProtocols/MVMCoreViewManagerProtocol.h +++ b/MVMCore/MVMCore/MainProtocols/MVMCoreViewManagerProtocol.h @@ -19,7 +19,7 @@ - (nullable NSArray*)getAccessibilityElements; //AccessibilityElements that are owned by Manager. -/// Attempt to navigate to the controller. Return true if navigation occured. +/// Attempt to navigate to the controller. Return the controller that we navigated to if successful. - (void)navigateToViewControllerOfPageType:(nonnull NSString *)pageType controllerType:(_Nullable Class)controllerType completionHandler:(void (^ __nullable)(UIViewController * _Nullable viewController))completionHandler; @optional diff --git a/MVMCore/MVMCore/ViewControllerMapping/MVMCoreViewControllerMappingObject.h b/MVMCore/MVMCore/ViewControllerMapping/MVMCoreViewControllerMappingObject.h index 27b6078..58e4f2e 100644 --- a/MVMCore/MVMCore/ViewControllerMapping/MVMCoreViewControllerMappingObject.h +++ b/MVMCore/MVMCore/ViewControllerMapping/MVMCoreViewControllerMappingObject.h @@ -47,6 +47,9 @@ // For pages external to the mobile first framework to be added to the view controller mapping. - (void)addToTemplateViewControllerMapping:(nullable NSDictionary *>*)map; +// Transition function: A mix of new and legacy. +- (nullable NSObject *)getViewControllerMappingForTemplate:(nullable NSString *)templateID pageType:(nullable NSString *)pageType; + // Transition function: A mix of new and legacy. - (nullable UIViewController *)createMFViewControllerOfTemplate:(nullable NSString *)templateID pageType:(nullable NSString *)pageType; diff --git a/MVMCore/MVMCore/ViewControllerMapping/MVMCoreViewControllerMappingObject.m b/MVMCore/MVMCore/ViewControllerMapping/MVMCoreViewControllerMappingObject.m index f79d501..7d57034 100644 --- a/MVMCore/MVMCore/ViewControllerMapping/MVMCoreViewControllerMappingObject.m +++ b/MVMCore/MVMCore/ViewControllerMapping/MVMCoreViewControllerMappingObject.m @@ -46,6 +46,16 @@ } } +- (nullable NSObject *)getViewControllerMappingForTemplate:(nullable NSString *)templateID pageType:(nullable NSString *)pageType { + if (templateID) { + return [self getViewControllerMappingForTemplate:templateID]; + } else if (pageType) { + return [self getViewControllerMappingForPageType:pageType]; + } else { + return nil; + } +} + // Transition function: A mix of new and legacy. - (nullable UIViewController *)createMFViewControllerOfTemplate:(nullable NSString *)templateID pageType:(nullable NSString *)pageType { if (templateID) { From 64aba6a1008c2420557b2af5d94cc8e645d569a5 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Wed, 3 Apr 2024 21:49:19 -0400 Subject: [PATCH 14/15] Digital PCT265 story MVAPCT-48 - Bug fixes and optimizations --- MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift | 2 +- .../LoadHandling/MVMCoreLoadRequestOperation+Extension.swift | 1 + MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.h | 1 - MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m | 1 + 4 files changed, 3 insertions(+), 2 deletions(-) diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift index 475b9da..930306a 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift @@ -83,7 +83,7 @@ open class ActionOpenPageHandler: MVMCoreJSONActionHandlerProtocol { } // Adds any client parameters to the request parameters. if let parametersToFetch = model.clientParameters, - let fetchedParameters = try await ClientParameterHandler().getClientParameters( + let fetchedParameters = await ClientParameterHandler().getClientParameters( with: parametersToFetch, requestParameters: requestParameters.parameters as? [String : Any] ?? [:], actionId: MVMCoreActionHandler.getUUID(additionalData: additionalData) ?? "unknown", diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation+Extension.swift b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation+Extension.swift index 468bf40..53a0e86 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation+Extension.swift +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation+Extension.swift @@ -69,6 +69,7 @@ public extension MVMCoreLoadRequestOperation { return nil } MVMCoreLoggingHandler.logDebugMessage(withDelegate: "\(type(of: self)): Navigated to controller. pageType:\(pageType) controllerType:\(String(describing: controllerType))") + stopLoadingAnimationIfNeeded() return viewController } diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.h b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.h index fccbe82..c7d450b 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.h +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.h @@ -19,7 +19,6 @@ @interface MVMCoreLoadRequestOperation : MVMCoreOperation @property (nullable, strong, nonatomic) MVMCoreRequestParameters *requestParameters; -/// For load objects as in input parameter. Does not attach self generated load objects. @property (nullable, strong, nonatomic) MVMCoreLoadObject *loadObject; @property (nullable, strong, nonatomic) NSDictionary *dataForPage; @property (nullable, strong, nonatomic) DelegateObject *delegateObject; diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m index 78108f9..699df7b 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m @@ -147,6 +147,7 @@ // Create a load object from any data we fetched. MVMCoreLoadObject *loadObject = [self createLoadObjectWithPageFromCache:pageFromCache modulesFromCache:modulesFromCache]; + self.loadObject = loadObject; // Check if we need to go to server for missing data. MVMCoreRequestParameters *requestForMissingData = [MVMCoreLoadRequestOperation createRequestForDataWithLoadObject:loadObject]; From 19c277d68e182b0946fab80339f0d1780c9f3c23 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Thu, 4 Apr 2024 17:33:05 -0400 Subject: [PATCH 15/15] Digital PCT265 story MVAPCT-48 - exposing constants. --- MVMCore/MVMCore/OtherHandlers/MVMCoreCache.h | 3 +++ MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m | 9 ++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.h b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.h index 6319fa5..5d2a48c 100644 --- a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.h +++ b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.h @@ -12,6 +12,9 @@ #import @class MVMCoreErrorObject; +extern NSString * _Nonnull const KeyCachePolicy; +extern NSString * _Nonnull const KeyCacheExpiry; + //block returned when getting image //parameters are UIImage object for the image, NSData for gif images, UIImage object for the image, A BOOL to indicate if it is a fall back image. typedef void(^MVMCoreGetImageBlock)(UIImage * _Nullable, NSData * _Nullable, BOOL); diff --git a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m index 3f1fa92..f2af3c3 100644 --- a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m +++ b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m @@ -17,6 +17,9 @@ #import "MVMCoreErrorConstants.h" #import "MVMCoreLoggingHandlerHelper.h" +NSString * _Nonnull const KeyCachePolicy = @"cachePolicy"; +NSString * _Nonnull const KeyCacheExpiry = @"expiry"; + @interface MVMCoreCache () // The cache for json. @@ -113,13 +116,13 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; } - (nonnull NSDate *)getExpirationDateForJSON:(nonnull NSDictionary *)jsonDictionary { - NSDictionary *cachePolicy = [jsonDictionary dict:@"cachePolicy"]; - NSTimeInterval interval = [[cachePolicy string:@"expiry"] doubleValue] / 1000; + NSDictionary *cachePolicy = [jsonDictionary dict:KeyCachePolicy]; + NSTimeInterval interval = [[cachePolicy string:KeyCacheExpiry] doubleValue] / 1000; return [NSDate dateWithTimeIntervalSince1970:interval]; } - (BOOL)shouldPersistentlyCacheJSON:(nonnull NSDictionary *)jsonDictionary { - NSDictionary *cachePolicy = [jsonDictionary dict:@"cachePolicy"]; + NSDictionary *cachePolicy = [jsonDictionary dict:KeyCachePolicy]; if (!cachePolicy || ![cachePolicy boolForKey:@"persist"]) { return NO; }