From 37ae8eb2be9ac93607cb6bf0701923cbfa4ebbdc Mon Sep 17 00:00:00 2001 From: Nikola Zagorchev Date: Tue, 21 Mar 2023 20:06:31 +0200 Subject: [PATCH 1/3] Allow executing ActionManager decision handlers on background thread --- .../Features/Actions/LPActionContext.m | 8 +- .../Actions/ActionManager+Executor.swift | 140 ++++++++++-------- .../Actions/ActionManager+Triggering.swift | 50 ++++--- .../ClassesSwift/Actions/ActionManager.swift | 2 + 4 files changed, 117 insertions(+), 83 deletions(-) diff --git a/LeanplumSDK/LeanplumSDK/Classes/Features/Actions/LPActionContext.m b/LeanplumSDK/LeanplumSDK/Classes/Features/Actions/LPActionContext.m index d9ebf507..64c63697 100644 --- a/LeanplumSDK/LeanplumSDK/Classes/Features/Actions/LPActionContext.m +++ b/LeanplumSDK/LeanplumSDK/Classes/Features/Actions/LPActionContext.m @@ -578,12 +578,16 @@ + (void)sortByPriority:(NSMutableArray *)actionContexts - (void)actionDismissed { - self.actionDidDismiss(); + if (self.actionDidDismiss) { + self.actionDidDismiss(); + } else { + LPLog(LPError, @"%@: actionDidDismiss not set for context %@", [self class], self); + } } -(NSString *)description { - return [NSString stringWithFormat:@"%@:%@", self.name, self.messageId]; + return [NSString stringWithFormat:@"<%p>%@:%@", self, self.name, self.messageId]; } @end diff --git a/LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager+Executor.swift b/LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager+Executor.swift index e849dbf8..ca98f0fe 100644 --- a/LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager+Executor.swift +++ b/LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager+Executor.swift @@ -3,7 +3,7 @@ // Leanplum // // Created by Milos Jakovljevic on 2.01.22. -// Copyright © 2022 Leanplum. All rights reserved. +// Copyright © 2023 Leanplum. All rights reserved. import Foundation @@ -29,7 +29,7 @@ extension ActionManager { } return } - + // gets the next action from the queue state.currentAction = queue.pop() guard let action = state.currentAction else { @@ -45,71 +45,85 @@ extension ActionManager { performAvailableActions() return } - + // decide if we are going to display the message // by calling delegate and let it decide what are we supposed to do - let messageDisplayDecision = shouldDisplayMessage?(action.context) - - // if message is discarded, early exit - if case .discard = messageDisplayDecision?.decision { - state.currentAction = nil - performAvailableActions() - return - } - - // if message is delayed, add it to the scheduler to be delayed - // by the amount of seconds, and exit - if case .delay(let amount) = messageDisplayDecision?.decision { - Log.debug("[ActionManager]: delaying action: \(action.context) for \(amount)s.") - - if amount > 0 { - // Schedule for delayed time - scheduler.schedule(action: action, delay: amount) - } else { - // Insert in delayed queue - delayedQueue.pushBack(action) + shouldDisplayMessage(context: action.context) { [weak self] messageDisplayDecision in + // if message is discarded, early exit + if case .discard = messageDisplayDecision?.decision { + self?.state.currentAction = nil + self?.performAvailableActions() + return } - state.currentAction = nil - performAvailableActions() - return - } - - // logic: - // 1) ask client to show view controller - // 2) wait for client to execute action - // 3) ask and wait for client to dismiss view controller - - // get the action definition - let definition = definitions.first { $0.name == action.context.name } - - // 2) set the execute block which will be called by client - action.context.actionDidExecute = { [weak self] context in - Log.debug("[ActionManager]: actionDidExecute: \(context).") - self?.onMessageAction?(context.name, context) - } - - // 3) set the dismiss block which will be called by client - action.context.actionDidDismiss = { [weak self] in - Log.debug("[ActionManager]: actionDidDismiss: \(action.context).") - self?.onMessageDismissed?(action.context) - self?.state.currentAction = nil - self?.performAvailableActions() + + // if message is delayed, add it to the scheduler to be delayed + // by the amount of seconds, and exit + if case .delay(let amount) = messageDisplayDecision?.decision { + Log.debug("[ActionManager]: delaying action: \(action.context) for \(amount)s.") + + if amount > 0 { + // Schedule for delayed time + self?.scheduler.schedule(action: action, delay: amount) + } else { + // Insert in delayed queue + self?.delayedQueue.pushBack(action) + } + self?.state.currentAction = nil + self?.performAvailableActions() + return + } + + // logic: + // 1) ask client to show view controller + // 2) wait for client to execute action + // 3) ask and wait for client to dismiss view controller + + // get the action definition + let definition = self?.definitions.first { $0.name == action.context.name } + + // 2) set the execute block which will be called by client + action.context.actionDidExecute = { [weak self] context in + Log.debug("[ActionManager]: actionDidExecute: \(context).") + self?.onMessageAction?(context.name, context) + } + + // 3) set the dismiss block which will be called by client + action.context.actionDidDismiss = { [weak self] in + Log.debug("[ActionManager]: actionDidDismiss: \(action.context).") + self?.onMessageDismissed?(action.context) + self?.state.currentAction = nil + self?.performAvailableActions() + } + + // 1) ask to present, return if its not + guard let handled = definition?.presentAction?(action.context), handled else { + Log.debug("[ActionManager]: action NOT presented: \(action.context).") + self?.state.currentAction = nil + self?.performAvailableActions() + return + } + Log.info("[ActionManager]: action presented: \(action.context).") + + // iff handled track that message has been displayed + // propagate event that message is displayed + self?.onMessageDisplayed?(action.context) + + // record the impression + self?.recordImpression(action: action) } - - // 1) ask to present, return if its not - guard let handled = definition?.presentAction?(action.context), handled else { - Log.debug("[ActionManager]: action NOT presented: \(action.context).") - state.currentAction = nil - performAvailableActions() - return + } + + func shouldDisplayMessage(context: ActionContext, callback: @escaping (MessageDisplayChoice?) -> ()) { + if useAsyncDecisionHandlers { + DispatchQueue.global(qos: .background).async { [weak self] in + let messageDisplayDecision = self?.shouldDisplayMessage?(context) + DispatchQueue.main.async { + callback(messageDisplayDecision) + } + } + } else { + let messageDisplayDecision = self.shouldDisplayMessage?(context) + callback(messageDisplayDecision) } - Log.info("[ActionManager]: action presented: \(action.context).") - - // iff handled track that message has been displayed - // propagate event that message is displayed - onMessageDisplayed?(action.context) - - // record the impression - recordImpression(action: action) } } diff --git a/LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager+Triggering.swift b/LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager+Triggering.swift index 39d52e51..b58cf50a 100644 --- a/LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager+Triggering.swift +++ b/LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager+Triggering.swift @@ -3,7 +3,7 @@ // Leanplum // // Created by Milos Jakovljevic on 2.01.22. -// Copyright © 2022 Leanplum. All rights reserved. +// Copyright © 2023 Leanplum. All rights reserved. import Foundation @@ -15,14 +15,14 @@ extension ActionManager { var name: String { switch self { - case .high: - return "high" - default: - return "default" + case .high: + return "high" + default: + return "default" } } } - + @objc public func trigger(contexts: [Any], priority: Priority = .default, trigger: ActionsTrigger? = nil) { guard let contexts = contexts as? [ActionContext] else { return @@ -32,21 +32,36 @@ extension ActionManager { guard let firstContext = contexts.first else { return } - + // By default, add only one message to queue if `prioritizeMessages` is not implemented // This ensures backwards compatibility - let filteredActions = prioritizeMessages?(contexts, trigger) ?? [firstContext] - let actions: [Action] = filteredActions.map { - .action(context: $0) - } - - Log.debug("[ActionManager]: triggering actions with priority: \(priority.name).") - - switch priority { + prioritizeMessages(contexts: contexts, defaultContexts: [firstContext], trigger: trigger) { [self] filteredActions in + let actions: [Action] = filteredActions.map { + .action(context: $0) + } + + Log.debug("[ActionManager]: triggering actions with priority: \(priority.name).") + + switch priority { case .high: - insertActions(actions: actions) + self.insertActions(actions: actions) default: - appendActions(actions: actions) + self.appendActions(actions: actions) + } + } + } + + func prioritizeMessages(contexts: [ActionContext], defaultContexts: [ActionContext], trigger: ActionsTrigger? = nil, callback: @escaping ([ActionContext]) -> ()) { + if useAsyncDecisionHandlers { + DispatchQueue.global(qos: .background).async { [weak self] in + let filteredActions = self?.prioritizeMessages?(contexts, trigger) ?? defaultContexts + DispatchQueue.main.async { + callback(filteredActions) + } + } + } else { + let filteredActions = self.prioritizeMessages?(contexts, trigger) ?? defaultContexts + callback(filteredActions) } } @@ -90,4 +105,3 @@ extension ActionManager { } } } - diff --git a/LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager.swift b/LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager.swift index 3ef2cd59..8ba5299b 100644 --- a/LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager.swift +++ b/LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager.swift @@ -14,6 +14,8 @@ import Foundation /// `ActionManager.Configuration` of the `ActionManager` /// Set a new configuration to override a configuration option public var configuration: Configuration = .default + + public var useAsyncDecisionHandlers = false lazy var queue: Queue = Queue() lazy var delayedQueue: Queue = Queue() From 07732dccec90fd344f1d40c994d00c8525424621 Mon Sep 17 00:00:00 2001 From: Nikola Zagorchev Date: Fri, 31 Mar 2023 22:24:43 +0300 Subject: [PATCH 2/3] update version --- Leanplum-iOS-Location.podspec | 2 +- Leanplum-iOS-LocationAndBeacons.podspec | 2 +- LeanplumSDK/LeanplumSDK/Classes/Internal/LPConstants.h | 2 +- sdk-version.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Leanplum-iOS-Location.podspec b/Leanplum-iOS-Location.podspec index 2f93cd9e..42cfa359 100644 --- a/Leanplum-iOS-Location.podspec +++ b/Leanplum-iOS-Location.podspec @@ -14,7 +14,7 @@ Pod::Spec.new do |s| s.source_files = 'LeanplumSDKLocation/LeanplumSDKLocation/Classes/**/*' s.frameworks = 'CoreLocation' s.documentation_url = 'https://docs.leanplum.com/' - s.dependency 'Leanplum-iOS-SDK', "~> 6.0.0" + s.dependency 'Leanplum-iOS-SDK', "~> 6.0.0-beta" s.module_name = 'LeanplumLocation' s.swift_versions = '5.0' end diff --git a/Leanplum-iOS-LocationAndBeacons.podspec b/Leanplum-iOS-LocationAndBeacons.podspec index d5e83653..d16eb08b 100644 --- a/Leanplum-iOS-LocationAndBeacons.podspec +++ b/Leanplum-iOS-LocationAndBeacons.podspec @@ -14,7 +14,7 @@ Pod::Spec.new do |s| s.source_files = 'LeanplumSDKLocation/LeanplumSDKLocation/Classes/**/*' s.frameworks = 'CoreLocation' s.documentation_url = 'https://docs.leanplum.com/' - s.dependency 'Leanplum-iOS-SDK', "~> 6.0.0" + s.dependency 'Leanplum-iOS-SDK', "~> 6.0.0-beta" s.module_name = 'LeanplumLocationAndBeacons' s.pod_target_xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => 'LP_BEACON=1' } s.swift_versions = '5.0' diff --git a/LeanplumSDK/LeanplumSDK/Classes/Internal/LPConstants.h b/LeanplumSDK/LeanplumSDK/Classes/Internal/LPConstants.h index bcfb279f..4d7c2b4f 100644 --- a/LeanplumSDK/LeanplumSDK/Classes/Internal/LPConstants.h +++ b/LeanplumSDK/LeanplumSDK/Classes/Internal/LPConstants.h @@ -38,7 +38,7 @@ #define IS_NOOP (IS_JAILBROKEN || [LPConstantsState sharedState].isTestMode || [LPConstantsState sharedState].isInPermanentFailureState) #define RETURN_IF_NOOP if (IS_NOOP) return -#define LEANPLUM_SDK_VERSION @"6.0.4" +#define LEANPLUM_SDK_VERSION @"6.0.5-beta1" #define LEANPLUM_CLIENT @"ios" #define LEANPLUM_SUPPORTED_ENCODING @"gzip" diff --git a/sdk-version.txt b/sdk-version.txt index 1aa5e414..afdbc075 100644 --- a/sdk-version.txt +++ b/sdk-version.txt @@ -1 +1 @@ -6.0.4 +6.0.5-beta1 From e224d9dc4303f7848416d66f263297069a256b21 Mon Sep 17 00:00:00 2001 From: Nikola Zagorchev Date: Thu, 6 Apr 2023 19:27:23 +0300 Subject: [PATCH 3/3] Revert "update version" This reverts commit 07732dccec90fd344f1d40c994d00c8525424621. --- Leanplum-iOS-Location.podspec | 2 +- Leanplum-iOS-LocationAndBeacons.podspec | 2 +- LeanplumSDK/LeanplumSDK/Classes/Internal/LPConstants.h | 2 +- sdk-version.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Leanplum-iOS-Location.podspec b/Leanplum-iOS-Location.podspec index 42cfa359..2f93cd9e 100644 --- a/Leanplum-iOS-Location.podspec +++ b/Leanplum-iOS-Location.podspec @@ -14,7 +14,7 @@ Pod::Spec.new do |s| s.source_files = 'LeanplumSDKLocation/LeanplumSDKLocation/Classes/**/*' s.frameworks = 'CoreLocation' s.documentation_url = 'https://docs.leanplum.com/' - s.dependency 'Leanplum-iOS-SDK', "~> 6.0.0-beta" + s.dependency 'Leanplum-iOS-SDK', "~> 6.0.0" s.module_name = 'LeanplumLocation' s.swift_versions = '5.0' end diff --git a/Leanplum-iOS-LocationAndBeacons.podspec b/Leanplum-iOS-LocationAndBeacons.podspec index d16eb08b..d5e83653 100644 --- a/Leanplum-iOS-LocationAndBeacons.podspec +++ b/Leanplum-iOS-LocationAndBeacons.podspec @@ -14,7 +14,7 @@ Pod::Spec.new do |s| s.source_files = 'LeanplumSDKLocation/LeanplumSDKLocation/Classes/**/*' s.frameworks = 'CoreLocation' s.documentation_url = 'https://docs.leanplum.com/' - s.dependency 'Leanplum-iOS-SDK', "~> 6.0.0-beta" + s.dependency 'Leanplum-iOS-SDK', "~> 6.0.0" s.module_name = 'LeanplumLocationAndBeacons' s.pod_target_xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => 'LP_BEACON=1' } s.swift_versions = '5.0' diff --git a/LeanplumSDK/LeanplumSDK/Classes/Internal/LPConstants.h b/LeanplumSDK/LeanplumSDK/Classes/Internal/LPConstants.h index 4d7c2b4f..bcfb279f 100644 --- a/LeanplumSDK/LeanplumSDK/Classes/Internal/LPConstants.h +++ b/LeanplumSDK/LeanplumSDK/Classes/Internal/LPConstants.h @@ -38,7 +38,7 @@ #define IS_NOOP (IS_JAILBROKEN || [LPConstantsState sharedState].isTestMode || [LPConstantsState sharedState].isInPermanentFailureState) #define RETURN_IF_NOOP if (IS_NOOP) return -#define LEANPLUM_SDK_VERSION @"6.0.5-beta1" +#define LEANPLUM_SDK_VERSION @"6.0.4" #define LEANPLUM_CLIENT @"ios" #define LEANPLUM_SUPPORTED_ENCODING @"gzip" diff --git a/sdk-version.txt b/sdk-version.txt index afdbc075..1aa5e414 100644 --- a/sdk-version.txt +++ b/sdk-version.txt @@ -1 +1 @@ -6.0.5-beta1 +6.0.4