diff --git a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.h b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.h index 2f41e93..d46fa63 100644 --- a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.h +++ b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.h @@ -32,7 +32,7 @@ extern NSString * _Nonnull const KeyActionTypeOpen; - (void)synchronouslyHandleAction:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; /// Iterates through the clientParameters list. Gets values from the individual handlers and attaches the parameters to extraParameters. -- (void)setClientParameter:(nullable NSDictionary *)actionInformation completionHandler:(nonnull void (^)(NSDictionary * _Nullable jsonDictionary))completionHandler; +- (void)getClientParameter:(nullable NSDictionary *)clientParametersMap requestParameters:(nullable NSDictionary *)requestParameters showLoadingOverlay:(BOOL)showLoadingOverlay completionHandler:(nonnull void (^)(NSDictionary * _Nullable jsonDictionary))completionHandler; #pragma mark - Actions /// by default, returns the original RequestParameter that passed in. Can be overriden for some generic updates to the RequestParameter before handle open page action gets called. @@ -97,7 +97,7 @@ extern NSString * _Nonnull const KeyActionTypeOpen; + (void)defaultLogAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; /// Sends the request to the load handler. -+ (void)defaultHandleOpenPageForRequestParameters:(nonnull MVMCoreRequestParameters *)requestParameters additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; ++ (void)defaultHandleOpenPageForRequestParameters:(nonnull MVMCoreRequestParameters *)requestParameters actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; /// By default, throws an error, calling defaultHandleActionError. + (void)defaultHandleUnknownActionType:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; diff --git a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.m b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.m index ddb1fab..8b9a3aa 100644 --- a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.m +++ b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.m @@ -93,22 +93,19 @@ NSString * const KeyActionTypeOpen = @"openPage"; } } -- (void)setClientParameter:(nullable NSDictionary *)actionInformation completionHandler:(nonnull void (^)(NSDictionary * _Nullable jsonDictionary))completionHandler { +- (void)getClientParameter:(nullable NSDictionary *)clientParametersMap requestParameters:(nullable NSDictionary *)requestParameters showLoadingOverlay:(BOOL)showLoadingOverlay completionHandler:(nonnull void (^)(NSDictionary * _Nullable parameters))completionHandler { - NSDictionary *clientParametersMap = [actionInformation dict:KeyClientParameters]; if (!clientParametersMap) { - completionHandler(actionInformation); + completionHandler(nil); return; } - - BOOL isBackgroudRequest = [actionInformation boolForKey:@"background"]; - - if (!isBackgroudRequest) { + + if (showLoadingOverlay) { [[MVMCoreLoadingOverlayHandler sharedLoadingOverlay] startLoading]; } void (^stopLoadingOverlay)(void) = ^(void) { - if (!isBackgroudRequest) { + if (showLoadingOverlay) { [[MVMCoreLoadingOverlayHandler sharedLoadingOverlay] stopLoading:true]; } }; @@ -116,27 +113,23 @@ NSString * const KeyActionTypeOpen = @"openPage"; NSError *error = nil; [MVMCoreLoggingHandler logDebugMessageWithDelegate:@"Fetching client parameters"]; [[[MVMCoreObject sharedInstance] clientParameterRegistry] getParametersWith:clientParametersMap + requestParameters:requestParameters error:&error completionHandler:^(NSDictionary * _Nullable clientParameters) { [MVMCoreLoggingHandler logDebugMessageWithDelegate:@"Finshed fetching client parameters"]; if (clientParameters) { - NSMutableDictionary *actionWithClientParameters = [actionInformation mutableCopy]; - NSMutableDictionary *extraParameters = [clientParameters mutableCopy]; - [extraParameters addEntriesFromDictionary:[actionWithClientParameters dictionaryForKey:KeyExtraParameters]]; - actionWithClientParameters[KeyExtraParameters] = extraParameters; - stopLoadingOverlay(); - completionHandler(actionWithClientParameters); + completionHandler(clientParameters); } else { [MVMCoreLoggingHandler logDebugMessageWithDelegate:@"No client parameters"]; stopLoadingOverlay(); - completionHandler(actionInformation); + completionHandler(nil); } }]; if (error) { stopLoadingOverlay(); - completionHandler(actionInformation); + completionHandler(nil); [MVMCoreLoggingHandler addErrorToLog:[MVMCoreErrorObject createErrorObjectForNSError:error location:@"MVMCoreActionHandler->setClientParameter"]]; } } @@ -168,20 +161,17 @@ NSString * const KeyActionTypeOpen = @"openPage"; return; } - __weak typeof(self) weakSelf = self; - void (^performAction)(NSDictionary*) = ^(NSDictionary* actionMap) { - MVMCoreRequestParameters *requestParameters = [[MVMCoreRequestParameters alloc] initWithActionMap:actionMap]; - - [weakSelf updateRequestParametersBeforeHandleOpenPageAction:requestParameters callBack:^(MVMCoreRequestParameters * _Nonnull requestParameters) { - if ([delegateObject.actionDelegate respondsToSelector:@selector(handleOpenPageForRequestParameters:actionInformation:additionalData:)]) { - [delegateObject.actionDelegate handleOpenPageForRequestParameters:requestParameters actionInformation:actionInformation additionalData:additionalData]; - } else { - [MVMCoreActionHandler defaultHandleOpenPageForRequestParameters:requestParameters additionalData:additionalData delegateObject:delegateObject]; - } - }]; - }; - - [self setClientParameter:actionInformation completionHandler:performAction]; + MVMCoreRequestParameters *requestParameters = [[MVMCoreRequestParameters alloc] initWithActionMap:actionInformation]; + [self updateRequestParametersBeforeHandleOpenPageAction:requestParameters callBack:^(MVMCoreRequestParameters * _Nonnull requestParameters) { + if ([delegateObject.actionDelegate respondsToSelector:@selector(handleOpenPageForRequestParameters:actionInformation:additionalData:)]) { + [delegateObject.actionDelegate handleOpenPageForRequestParameters:requestParameters actionInformation:actionInformation additionalData:additionalData]; + } else { + [MVMCoreActionHandler defaultHandleOpenPageForRequestParameters:requestParameters + actionInformation:actionInformation + additionalData:additionalData + delegateObject:delegateObject]; + } + }]; } - (void)shareAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { @@ -278,7 +268,10 @@ NSString * const KeyActionTypeOpen = @"openPage"; if ([delegateObject.actionDelegate respondsToSelector:@selector(handleOpenPageForRequestParameters:actionInformation:additionalData:)]) { [delegateObject.actionDelegate handleOpenPageForRequestParameters:requestParameters actionInformation:actionInformation additionalData:dataForPage]; } else { - [MVMCoreActionHandler defaultHandleOpenPageForRequestParameters:requestParameters additionalData:additionalData delegateObject:delegateObject]; + [MVMCoreActionHandler defaultHandleOpenPageForRequestParameters:requestParameters + actionInformation:actionInformation + additionalData:additionalData + delegateObject:delegateObject]; } }]; }]; @@ -340,26 +333,35 @@ NSString * const KeyActionTypeOpen = @"openPage"; - (void)linkAwayAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { __weak typeof(self) weakSelf = self; - void (^performAction)(NSDictionary*) = ^(NSDictionary* actionMap) { + void (^performAction)(NSDictionary*) = ^(NSDictionary* extraParamters) { + + NSMutableDictionary *actionWithClientParameters = [actionInformation mutableCopy]; + NSMutableDictionary *extraParametersT = [extraParamters mutableCopy]; + [extraParametersT addEntriesFromDictionary:[actionWithClientParameters dictionaryForKey:KeyExtraParameters]]; + actionWithClientParameters[KeyExtraParameters] = extraParametersT; + // Gets the app url NSURL *appURL = nil; - NSString *appURLString = [actionMap string:KeyLinkAwayAppURL]; + NSString *appURLString = [actionWithClientParameters string:KeyLinkAwayAppURL]; if (appURLString.length > 0) { appURL = [NSURL URLWithString:appURLString]; } // Gets the browser url NSURL *otherURL = nil; - NSString *otherURLString = [actionMap string:KeyLinkAwayURL]; + NSString *otherURLString = [actionWithClientParameters string:KeyLinkAwayURL]; if (otherURLString.length > 0) { otherURL = [NSURL URLWithString:otherURLString]; } // Provide the URL and App URL to be modified if needed by a subclass or delegate. - [weakSelf prepareLinkAwayWithURL:otherURL appURL:appURL actionInformation:actionMap additionalData:additionalData delegateObject:delegateObject]; + [weakSelf prepareLinkAwayWithURL:otherURL appURL:appURL actionInformation:actionWithClientParameters additionalData:additionalData delegateObject:delegateObject]; }; - [self setClientParameter:actionInformation completionHandler:performAction]; + [self getClientParameter:[actionInformation dict:KeyClientParameters] + requestParameters:nil + showLoadingOverlay:true + completionHandler:performAction]; } - (void)prepareLinkAwayWithURL:(nullable NSURL *)url appURL:(nullable NSURL *)appURL actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { @@ -418,8 +420,20 @@ NSString * const KeyActionTypeOpen = @"openPage"; // Currently no default log action but this will eventually be server driven. } -+ (void)defaultHandleOpenPageForRequestParameters:(nonnull MVMCoreRequestParameters *)requestParameters additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - [[MVMCoreLoadHandler sharedGlobal] loadRequest:requestParameters dataForPage:additionalData delegateObject:delegateObject]; ++ (void)defaultHandleOpenPageForRequestParameters:(nonnull MVMCoreRequestParameters *)requestParameters actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { + + NSDictionary *clientParamters = [actionInformation dict:KeyClientParameters]; + if (clientParamters) { + [[MVMCoreActionHandler sharedActionHandler] getClientParameter:clientParamters + requestParameters: requestParameters.parameters + showLoadingOverlay: !requestParameters.backgroundRequest + completionHandler: ^(NSDictionary * _Nullable jsonDictionary) { + [requestParameters addRequestParameters:jsonDictionary]; + [[MVMCoreLoadHandler sharedGlobal] loadRequest:requestParameters dataForPage:additionalData delegateObject:delegateObject]; + }]; + } else { + [[MVMCoreLoadHandler sharedGlobal] loadRequest:requestParameters dataForPage:additionalData delegateObject:delegateObject]; + } } + (void)defaultHandleUnknownActionType:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { diff --git a/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterProtocol.swift b/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterProtocol.swift index 2c689ec..2cf156b 100644 --- a/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterProtocol.swift +++ b/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterProtocol.swift @@ -9,7 +9,33 @@ import Foundation public protocol ClientParameterProtocol { - init() static var name: String { get } - func fetchClientParameters(for paramModel: ClientParameterModelProtocol, timingOutIn timeout: Double, completionHandler:@escaping (AnyHashable?) -> ()) + + init(_ clientParameterModel: ClientParameterModelProtocol) + + var clientParameterModel: ClientParameterModelProtocol { get set } + + func fetchClientParameters(requestParameters: [String: Any], timingOutIn timeout: Double, completionHandler:@escaping ([String: AnyHashable]?) -> ()) + + /// Default parameter for timeout scenarios. It will use the protocol extension method bydefault. Can override to send custom values. + func valueOnTimeout() -> [String: AnyHashable] +} + +public extension ClientParameterProtocol { + + func valueOnTimeout() -> [String: AnyHashable] { + return [Self.name: "failed_to_collect"] + } + + /// The handler should call this method to pass the parameter back to the caller. + func returnParameters(_ isFlatMap: Bool, _ parameter: [String: AnyHashable]?, completionHandler: @escaping ([String: AnyHashable]?) -> ()) { + guard let parameter = parameter else { + return completionHandler(nil) + } + if isFlatMap { + completionHandler(parameter) + } else { + completionHandler([Self.name: parameter]) + } + } } diff --git a/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterRegistry.swift b/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterRegistry.swift index 1a4160d..a211675 100644 --- a/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterRegistry.swift +++ b/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterRegistry.swift @@ -23,9 +23,9 @@ import Foundation mapping[T.name] = handler } - open func createParametersHandler(_ actionType: String) -> ClientParameterProtocol? { - guard let parameterType = mapping[actionType] else { return nil } - return parameterType.init() + open func createParametersHandler(_ clientParameterModel: ClientParameterModelProtocol) -> ClientParameterProtocol? { + guard let parameterType = mapping[clientParameterModel.type] else { return nil } + return parameterType.init(clientParameterModel) } static func getClientParameterModel(_ clientParameters: [String: Any]) throws -> ClientParameterModel? { @@ -47,52 +47,67 @@ import Foundation /// ] ///} /// completionHandler can return flat dictinary or a map. It depends on the paramters handler - open func getParameters(with clientParameters: [String: Any], completionHandler:@escaping ([String: Any]?) -> ()) throws { + open func getParameters(with clientParameters: [String: Any], requestParameters: [String: Any], completionHandler:@escaping ([String: Any]?) -> ()) throws { guard let clientParameterModel = try ClientParameterRegistry.getClientParameterModel(clientParameters) else { completionHandler(nil) return } - var parametersList: [String: Any] = [:] let timeout = clientParameterModel.timeout ?? 30.0 let parametersWorkQueue = DispatchQueue(label: "com.mva.clientparameter") let group = DispatchGroup() - let defaultErrorString = "failed_to_collect." // Dispatch setup on queue to ensure setup is complete before completion callbacks. parametersWorkQueue.async(group: group, qos: .userInitiated) { [weak self] in guard let self = self else { return } + + var parameterHandlerList: [ClientParameterProtocol] = [] + + // Create the handler list so that same object can be used when merging. Merging needs default value in case of timeout + for parameterModel in clientParameterModel.list { + if let parameterHandler = self.createParametersHandler(parameterModel) { + parameterHandlerList.append(parameterHandler) + } + } + + var returnedList = [[String: AnyHashable]?](repeating: nil, count: parameterHandlerList.count) + + var mergedParametersList: [String: AnyHashable] { + var parametersList: [String: AnyHashable] = [:] + for (index, item) in returnedList.enumerated() { + let parameter = item ?? parameterHandlerList[index].valueOnTimeout() + parametersList = parametersList.merging(parameter) { (_, new) in new } + } + return parametersList + } + // Setup completion handlers. Barriered to ensure one happens after the other. var complete = false let timeoutWorkItem = DispatchWorkItem(qos: .userInitiated) { - completionHandler(parametersList); + completionHandler(mergedParametersList); complete = true } let completionWorkItem = DispatchWorkItem(qos: .userInitiated) { timeoutWorkItem.cancel() if !complete { // In the case of firing after timeout. - completionHandler(parametersList); + completionHandler(mergedParametersList); complete = true } } // Setup timeout. parametersWorkQueue.asyncAfter(deadline: .now() + .seconds(Int(timeout)), execute: timeoutWorkItem) - + // Setup the parameter execution. - for parameterModel in clientParameterModel.list { - // Setup default timeout / nil error. This will be replaced as parameters are collected. - parametersList[parameterModel.type] = ["error": defaultErrorString] + for (index, parameterHandler) in parameterHandlerList.enumerated() { group.enter() - // Dispatch asynchronous injection. - self.getParameterFromHandler(parameterModel, before: timeout) { (receivedParameter) in + parameterHandler.fetchClientParameters(requestParameters: requestParameters, + timingOutIn: timeout) { (receivedParameter) in // Queue the results for merge. parametersWorkQueue.async { - if let receivedParameter = receivedParameter { - parametersList[parameterModel.type] = receivedParameter - } + returnedList[index] = receivedParameter group.leave() // Leaving is only done after setup (barriered). } } @@ -103,13 +118,6 @@ import Foundation } } - func getParameterFromHandler( _ parameterModel: ClientParameterModelProtocol, before timeout: Double, completionHandler:@escaping (AnyHashable?) -> ()) { - guard let parameterHandler = createParametersHandler(parameterModel.type) else { - return completionHandler(nil) - } - parameterHandler.fetchClientParameters(for: parameterModel, timingOutIn: timeout, completionHandler: completionHandler) - } - /// Add all registry here. open func registerParameters() { }