Skip to content

Commit 0bcfe89

Browse files
authored
Add async functionality to ActionManager Queue (#560)
* Allow executing ActionManager decision handlers on background thread
1 parent c37f7f5 commit 0bcfe89

File tree

4 files changed

+117
-83
lines changed

4 files changed

+117
-83
lines changed

LeanplumSDK/LeanplumSDK/Classes/Features/Actions/LPActionContext.m

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -578,12 +578,16 @@ + (void)sortByPriority:(NSMutableArray *)actionContexts
578578

579579
- (void)actionDismissed
580580
{
581-
self.actionDidDismiss();
581+
if (self.actionDidDismiss) {
582+
self.actionDidDismiss();
583+
} else {
584+
LPLog(LPError, @"%@: actionDidDismiss not set for context %@", [self class], self);
585+
}
582586
}
583587

584588
-(NSString *)description
585589
{
586-
return [NSString stringWithFormat:@"%@:%@", self.name, self.messageId];
590+
return [NSString stringWithFormat:@"<%p>%@:%@", self, self.name, self.messageId];
587591
}
588592

589593
@end

LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager+Executor.swift

Lines changed: 77 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// Leanplum
44
//
55
// Created by Milos Jakovljevic on 2.01.22.
6-
// Copyright © 2022 Leanplum. All rights reserved.
6+
// Copyright © 2023 Leanplum. All rights reserved.
77

88
import Foundation
99

@@ -29,7 +29,7 @@ extension ActionManager {
2929
}
3030
return
3131
}
32-
32+
3333
// gets the next action from the queue
3434
state.currentAction = queue.pop()
3535
guard let action = state.currentAction else {
@@ -45,71 +45,85 @@ extension ActionManager {
4545
performAvailableActions()
4646
return
4747
}
48-
48+
4949
// decide if we are going to display the message
5050
// by calling delegate and let it decide what are we supposed to do
51-
let messageDisplayDecision = shouldDisplayMessage?(action.context)
52-
53-
// if message is discarded, early exit
54-
if case .discard = messageDisplayDecision?.decision {
55-
state.currentAction = nil
56-
performAvailableActions()
57-
return
58-
}
59-
60-
// if message is delayed, add it to the scheduler to be delayed
61-
// by the amount of seconds, and exit
62-
if case .delay(let amount) = messageDisplayDecision?.decision {
63-
Log.debug("[ActionManager]: delaying action: \(action.context) for \(amount)s.")
64-
65-
if amount > 0 {
66-
// Schedule for delayed time
67-
scheduler.schedule(action: action, delay: amount)
68-
} else {
69-
// Insert in delayed queue
70-
delayedQueue.pushBack(action)
51+
shouldDisplayMessage(context: action.context) { [weak self] messageDisplayDecision in
52+
// if message is discarded, early exit
53+
if case .discard = messageDisplayDecision?.decision {
54+
self?.state.currentAction = nil
55+
self?.performAvailableActions()
56+
return
7157
}
72-
state.currentAction = nil
73-
performAvailableActions()
74-
return
75-
}
76-
77-
// logic:
78-
// 1) ask client to show view controller
79-
// 2) wait for client to execute action
80-
// 3) ask and wait for client to dismiss view controller
81-
82-
// get the action definition
83-
let definition = definitions.first { $0.name == action.context.name }
84-
85-
// 2) set the execute block which will be called by client
86-
action.context.actionDidExecute = { [weak self] context in
87-
Log.debug("[ActionManager]: actionDidExecute: \(context).")
88-
self?.onMessageAction?(context.name, context)
89-
}
90-
91-
// 3) set the dismiss block which will be called by client
92-
action.context.actionDidDismiss = { [weak self] in
93-
Log.debug("[ActionManager]: actionDidDismiss: \(action.context).")
94-
self?.onMessageDismissed?(action.context)
95-
self?.state.currentAction = nil
96-
self?.performAvailableActions()
58+
59+
// if message is delayed, add it to the scheduler to be delayed
60+
// by the amount of seconds, and exit
61+
if case .delay(let amount) = messageDisplayDecision?.decision {
62+
Log.debug("[ActionManager]: delaying action: \(action.context) for \(amount)s.")
63+
64+
if amount > 0 {
65+
// Schedule for delayed time
66+
self?.scheduler.schedule(action: action, delay: amount)
67+
} else {
68+
// Insert in delayed queue
69+
self?.delayedQueue.pushBack(action)
70+
}
71+
self?.state.currentAction = nil
72+
self?.performAvailableActions()
73+
return
74+
}
75+
76+
// logic:
77+
// 1) ask client to show view controller
78+
// 2) wait for client to execute action
79+
// 3) ask and wait for client to dismiss view controller
80+
81+
// get the action definition
82+
let definition = self?.definitions.first { $0.name == action.context.name }
83+
84+
// 2) set the execute block which will be called by client
85+
action.context.actionDidExecute = { [weak self] context in
86+
Log.debug("[ActionManager]: actionDidExecute: \(context).")
87+
self?.onMessageAction?(context.name, context)
88+
}
89+
90+
// 3) set the dismiss block which will be called by client
91+
action.context.actionDidDismiss = { [weak self] in
92+
Log.debug("[ActionManager]: actionDidDismiss: \(action.context).")
93+
self?.onMessageDismissed?(action.context)
94+
self?.state.currentAction = nil
95+
self?.performAvailableActions()
96+
}
97+
98+
// 1) ask to present, return if its not
99+
guard let handled = definition?.presentAction?(action.context), handled else {
100+
Log.debug("[ActionManager]: action NOT presented: \(action.context).")
101+
self?.state.currentAction = nil
102+
self?.performAvailableActions()
103+
return
104+
}
105+
Log.info("[ActionManager]: action presented: \(action.context).")
106+
107+
// iff handled track that message has been displayed
108+
// propagate event that message is displayed
109+
self?.onMessageDisplayed?(action.context)
110+
111+
// record the impression
112+
self?.recordImpression(action: action)
97113
}
98-
99-
// 1) ask to present, return if its not
100-
guard let handled = definition?.presentAction?(action.context), handled else {
101-
Log.debug("[ActionManager]: action NOT presented: \(action.context).")
102-
state.currentAction = nil
103-
performAvailableActions()
104-
return
114+
}
115+
116+
func shouldDisplayMessage(context: ActionContext, callback: @escaping (MessageDisplayChoice?) -> ()) {
117+
if useAsyncDecisionHandlers {
118+
DispatchQueue.global(qos: .background).async { [weak self] in
119+
let messageDisplayDecision = self?.shouldDisplayMessage?(context)
120+
DispatchQueue.main.async {
121+
callback(messageDisplayDecision)
122+
}
123+
}
124+
} else {
125+
let messageDisplayDecision = self.shouldDisplayMessage?(context)
126+
callback(messageDisplayDecision)
105127
}
106-
Log.info("[ActionManager]: action presented: \(action.context).")
107-
108-
// iff handled track that message has been displayed
109-
// propagate event that message is displayed
110-
onMessageDisplayed?(action.context)
111-
112-
// record the impression
113-
recordImpression(action: action)
114128
}
115129
}

LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager+Triggering.swift

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// Leanplum
44
//
55
// Created by Milos Jakovljevic on 2.01.22.
6-
// Copyright © 2022 Leanplum. All rights reserved.
6+
// Copyright © 2023 Leanplum. All rights reserved.
77

88
import Foundation
99

@@ -15,14 +15,14 @@ extension ActionManager {
1515

1616
var name: String {
1717
switch self {
18-
case .high:
19-
return "high"
20-
default:
21-
return "default"
18+
case .high:
19+
return "high"
20+
default:
21+
return "default"
2222
}
2323
}
2424
}
25-
25+
2626
@objc public func trigger(contexts: [Any], priority: Priority = .default, trigger: ActionsTrigger? = nil) {
2727
guard let contexts = contexts as? [ActionContext] else {
2828
return
@@ -32,21 +32,36 @@ extension ActionManager {
3232
guard let firstContext = contexts.first else {
3333
return
3434
}
35-
35+
3636
// By default, add only one message to queue if `prioritizeMessages` is not implemented
3737
// This ensures backwards compatibility
38-
let filteredActions = prioritizeMessages?(contexts, trigger) ?? [firstContext]
39-
let actions: [Action] = filteredActions.map {
40-
.action(context: $0)
41-
}
42-
43-
Log.debug("[ActionManager]: triggering actions with priority: \(priority.name).")
44-
45-
switch priority {
38+
prioritizeMessages(contexts: contexts, defaultContexts: [firstContext], trigger: trigger) { [self] filteredActions in
39+
let actions: [Action] = filteredActions.map {
40+
.action(context: $0)
41+
}
42+
43+
Log.debug("[ActionManager]: triggering actions with priority: \(priority.name).")
44+
45+
switch priority {
4646
case .high:
47-
insertActions(actions: actions)
47+
self.insertActions(actions: actions)
4848
default:
49-
appendActions(actions: actions)
49+
self.appendActions(actions: actions)
50+
}
51+
}
52+
}
53+
54+
func prioritizeMessages(contexts: [ActionContext], defaultContexts: [ActionContext], trigger: ActionsTrigger? = nil, callback: @escaping ([ActionContext]) -> ()) {
55+
if useAsyncDecisionHandlers {
56+
DispatchQueue.global(qos: .background).async { [weak self] in
57+
let filteredActions = self?.prioritizeMessages?(contexts, trigger) ?? defaultContexts
58+
DispatchQueue.main.async {
59+
callback(filteredActions)
60+
}
61+
}
62+
} else {
63+
let filteredActions = self.prioritizeMessages?(contexts, trigger) ?? defaultContexts
64+
callback(filteredActions)
5065
}
5166
}
5267

@@ -90,4 +105,3 @@ extension ActionManager {
90105
}
91106
}
92107
}
93-

LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import Foundation
1414
/// `ActionManager.Configuration` of the `ActionManager`
1515
/// Set a new configuration to override a configuration option
1616
public var configuration: Configuration = .default
17+
18+
public var useAsyncDecisionHandlers = false
1719

1820
lazy var queue: Queue = Queue()
1921
lazy var delayedQueue: Queue = Queue()

0 commit comments

Comments
 (0)