Skip to content

Commit 2c0527f

Browse files
Merge pull request #555 from Iterable/tapash/mob-4408-coredata
Fix core data multi threading debug errors
2 parents 07173cd + 72846b7 commit 2c0527f

14 files changed

+333
-194
lines changed

swift-sdk.xcodeproj/xcshareddata/xcschemes/swift-sdk.xcscheme

+6
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,12 @@
183183
ReferencedContainer = "container:swift-sdk.xcodeproj">
184184
</BuildableReference>
185185
</BuildableProductRunnable>
186+
<CommandLineArguments>
187+
<CommandLineArgument
188+
argument = "-com.apple.CoreData.ConcurrencyDebug 1"
189+
isEnabled = "YES">
190+
</CommandLineArgument>
191+
</CommandLineArguments>
186192
</LaunchAction>
187193
<ProfileAction
188194
buildConfiguration = "Release"

swift-sdk/Internal/CoreDataUtil.swift

+10
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,13 @@ struct CoreDataUtil {
7272
}
7373
}
7474
}
75+
76+
extension NSManagedObjectContext {
77+
func performAndWait<T>(_ block: () throws -> T) throws -> T {
78+
var result: Result<T, Error>?
79+
performAndWait {
80+
result = Result { try block() }
81+
}
82+
return try result!.get()
83+
}
84+
}

swift-sdk/Internal/HealthMonitor.swift

+4-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ struct HealthMonitorDataProvider: HealthMonitorDataProviderProtocol {
1919
let maxTasks: Int
2020

2121
func countTasks() throws -> Int {
22-
return try persistenceContextProvider.newBackgroundContext().countTasks()
22+
let context = persistenceContextProvider.newBackgroundContext()
23+
return try context.performAndWait {
24+
try context.countTasks()
25+
}
2326
}
2427

2528
private let persistenceContextProvider: IterablePersistenceContextProvider

swift-sdk/Internal/IterableCoreDataPersistence.swift

+13-7
Original file line numberDiff line numberDiff line change
@@ -130,14 +130,16 @@ struct CoreDataPersistenceContext: IterablePersistenceContext {
130130
}
131131

132132
func nextTask() throws -> IterableTask? {
133-
let taskManagedObjects: [IterableTaskManagedObject] = try CoreDataUtil.findSortedEntities(context: managedObjectContext,
134-
entity: PersistenceConst.Entity.Task.name,
135-
column: PersistenceConst.Entity.Task.Column.scheduledAt,
136-
ascending: true,
137-
limit: 1)
138-
return taskManagedObjects.first.map(PersistenceHelper.task(from:))
133+
try performAndWait {
134+
let taskManagedObjects: [IterableTaskManagedObject] = try CoreDataUtil.findSortedEntities(context: managedObjectContext,
135+
entity: PersistenceConst.Entity.Task.name,
136+
column: PersistenceConst.Entity.Task.Column.scheduledAt,
137+
ascending: true,
138+
limit: 1)
139+
return taskManagedObjects.first.map(PersistenceHelper.task(from:))
140+
}
139141
}
140-
142+
141143
func findTask(withId id: String) throws -> IterableTask? {
142144
guard let taskManagedObject = try findTaskManagedObject(id: id) else {
143145
return nil
@@ -179,6 +181,10 @@ struct CoreDataPersistenceContext: IterablePersistenceContext {
179181
managedObjectContext.performAndWait(block)
180182
}
181183

184+
func performAndWait<T>(_ block: () throws -> T) throws -> T {
185+
try managedObjectContext.performAndWait(block)
186+
}
187+
182188
private let managedObjectContext: NSManagedObjectContext
183189
private let dateProvider: DateProviderProtocol
184190

swift-sdk/Internal/IterablePersistence.swift

+2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ protocol IterablePersistenceContext {
4646
func perform(_ block: @escaping () -> Void)
4747

4848
func performAndWait(_ block: () -> Void)
49+
50+
func performAndWait<T>(_ block: () throws -> T) throws -> T
4951
}
5052

5153
protocol IterablePersistenceContextProvider {

swift-sdk/Internal/IterableTaskRunner.swift

+4-2
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,10 @@ class IterableTaskRunner: NSObject {
244244

245245
private func deleteTask(task: IterableTask) {
246246
do {
247-
try persistenceContext.delete(task: task)
248-
try persistenceContext.save()
247+
try persistenceContext.performAndWait {
248+
try persistenceContext.delete(task: task)
249+
try persistenceContext.save()
250+
}
249251
} catch let error {
250252
ITBError(error.localizedDescription)
251253
healthMonitor.onDeleteError(task: task)

swift-sdk/Internal/IterableTaskScheduler.swift

+10-8
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,16 @@ class IterableTaskScheduler {
2323
do {
2424
let data = try JSONEncoder().encode(apiCallRequest)
2525

26-
try persistenceContext.create(task: IterableTask(id: taskId,
27-
name: apiCallRequest.getPath(),
28-
type: .apiCall,
29-
scheduledAt: scheduledAt ?? dateProvider.currentDate,
30-
data: data,
31-
requestedAt: dateProvider.currentDate))
32-
try persistenceContext.save()
33-
26+
try persistenceContext.performAndWait {
27+
try persistenceContext.create(task: IterableTask(id: taskId,
28+
name: apiCallRequest.getPath(),
29+
type: .apiCall,
30+
scheduledAt: scheduledAt ?? dateProvider.currentDate,
31+
data: data,
32+
requestedAt: dateProvider.currentDate))
33+
try persistenceContext.save()
34+
35+
}
3436
notificationCenter.post(name: .iterableTaskScheduled, object: self, userInfo: nil)
3537
} catch let error {
3638
healthMonitor.onScheduleError(apiCallRequest: apiCallRequest)

swift-sdk/Internal/Pending.swift

+13
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,19 @@ extension Pending {
165165
}
166166
}
167167

168+
extension Pending {
169+
static func inBackgroundThread<Value, Failure>(_ block: @escaping () -> Pending<Value, Failure>) -> Pending<Value, Failure> {
170+
let fulfill = Fulfill<Void, Failure>()
171+
DispatchQueue.global(qos: .background).async {
172+
fulfill.resolve(with: ())
173+
}
174+
175+
return fulfill.flatMap { _ in
176+
block()
177+
}
178+
}
179+
}
180+
168181
// This class takes the responsibility of setting value for Pending
169182
class Fulfill<Value, Failure>: Pending<Value, Failure> where Failure: Error {
170183
public init(value: Value? = nil) {

swift-sdk/Internal/RequestHandler.swift

+77-55
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,11 @@ class RequestHandler: RequestHandlerProtocol {
8989
func updateCart(items: [CommerceItem],
9090
onSuccess: OnSuccessHandler?,
9191
onFailure: OnFailureHandler?) -> Pending<SendRequestValue, SendRequestError> {
92-
chooseRequestProcessor().updateCart(items: items,
93-
onSuccess: onSuccess,
94-
onFailure: onFailure)
92+
Pending<SendRequestValue, SendRequestError>.inBackgroundThread {
93+
self.chooseRequestProcessor().updateCart(items: items,
94+
onSuccess: onSuccess,
95+
onFailure: onFailure)
96+
}
9597
}
9698

9799
@discardableResult
@@ -102,13 +104,15 @@ class RequestHandler: RequestHandlerProtocol {
102104
templateId: NSNumber?,
103105
onSuccess: OnSuccessHandler?,
104106
onFailure: OnFailureHandler?) -> Pending<SendRequestValue, SendRequestError> {
105-
chooseRequestProcessor().trackPurchase(total,
106-
items: items,
107-
dataFields: dataFields,
108-
campaignId: campaignId,
109-
templateId: templateId,
110-
onSuccess: onSuccess,
111-
onFailure: onFailure)
107+
Pending<SendRequestValue, SendRequestError>.inBackgroundThread {
108+
self.chooseRequestProcessor().trackPurchase(total,
109+
items: items,
110+
dataFields: dataFields,
111+
campaignId: campaignId,
112+
templateId: templateId,
113+
onSuccess: onSuccess,
114+
onFailure: onFailure)
115+
}
112116
}
113117

114118
@discardableResult
@@ -119,24 +123,28 @@ class RequestHandler: RequestHandlerProtocol {
119123
dataFields: [AnyHashable: Any]?,
120124
onSuccess: OnSuccessHandler?,
121125
onFailure: OnFailureHandler?) -> Pending<SendRequestValue, SendRequestError> {
122-
chooseRequestProcessor().trackPushOpen(campaignId,
123-
templateId: templateId,
124-
messageId: messageId,
125-
appAlreadyRunning: appAlreadyRunning,
126-
dataFields: dataFields,
127-
onSuccess: onSuccess,
128-
onFailure: onFailure)
126+
Pending<SendRequestValue, SendRequestError>.inBackgroundThread {
127+
self.chooseRequestProcessor().trackPushOpen(campaignId,
128+
templateId: templateId,
129+
messageId: messageId,
130+
appAlreadyRunning: appAlreadyRunning,
131+
dataFields: dataFields,
132+
onSuccess: onSuccess,
133+
onFailure: onFailure)
134+
}
129135
}
130136

131137
@discardableResult
132138
func track(event: String,
133139
dataFields: [AnyHashable: Any]?,
134140
onSuccess: OnSuccessHandler?,
135141
onFailure: OnFailureHandler?) -> Pending<SendRequestValue, SendRequestError> {
136-
chooseRequestProcessor().track(event: event,
137-
dataFields: dataFields,
138-
onSuccess: onSuccess,
139-
onFailure: onFailure)
142+
Pending<SendRequestValue, SendRequestError>.inBackgroundThread {
143+
self.chooseRequestProcessor().track(event: event,
144+
dataFields: dataFields,
145+
onSuccess: onSuccess,
146+
onFailure: onFailure)
147+
}
140148
}
141149

142150
@discardableResult
@@ -154,11 +162,13 @@ class RequestHandler: RequestHandlerProtocol {
154162
inboxSessionId: String?,
155163
onSuccess: OnSuccessHandler?,
156164
onFailure: OnFailureHandler?) -> Pending<SendRequestValue, SendRequestError> {
157-
chooseRequestProcessor().trackInAppOpen(message,
158-
location: location,
159-
inboxSessionId: inboxSessionId,
160-
onSuccess: onSuccess,
161-
onFailure: onFailure)
165+
Pending<SendRequestValue, SendRequestError>.inBackgroundThread {
166+
self.chooseRequestProcessor().trackInAppOpen(message,
167+
location: location,
168+
inboxSessionId: inboxSessionId,
169+
onSuccess: onSuccess,
170+
onFailure: onFailure)
171+
}
162172
}
163173

164174
@discardableResult
@@ -168,12 +178,14 @@ class RequestHandler: RequestHandlerProtocol {
168178
clickedUrl: String,
169179
onSuccess: OnSuccessHandler?,
170180
onFailure: OnFailureHandler?) -> Pending<SendRequestValue, SendRequestError> {
171-
chooseRequestProcessor().trackInAppClick(message,
172-
location: location,
173-
inboxSessionId: inboxSessionId,
174-
clickedUrl: clickedUrl,
175-
onSuccess: onSuccess,
176-
onFailure: onFailure)
181+
Pending<SendRequestValue, SendRequestError>.inBackgroundThread {
182+
self.chooseRequestProcessor().trackInAppClick(message,
183+
location: location,
184+
inboxSessionId: inboxSessionId,
185+
clickedUrl: clickedUrl,
186+
onSuccess: onSuccess,
187+
onFailure: onFailure)
188+
}
177189
}
178190

179191
@discardableResult
@@ -184,40 +196,48 @@ class RequestHandler: RequestHandlerProtocol {
184196
clickedUrl: String?,
185197
onSuccess: OnSuccessHandler?,
186198
onFailure: OnFailureHandler?) -> Pending<SendRequestValue, SendRequestError> {
187-
chooseRequestProcessor().trackInAppClose(message,
188-
location: location,
189-
inboxSessionId: inboxSessionId,
190-
source: source,
191-
clickedUrl: clickedUrl,
192-
onSuccess: onSuccess,
193-
onFailure: onFailure)
199+
Pending<SendRequestValue, SendRequestError>.inBackgroundThread {
200+
self.chooseRequestProcessor().trackInAppClose(message,
201+
location: location,
202+
inboxSessionId: inboxSessionId,
203+
source: source,
204+
clickedUrl: clickedUrl,
205+
onSuccess: onSuccess,
206+
onFailure: onFailure)
207+
}
194208
}
195209

196210
@discardableResult
197211
func track(inboxSession: IterableInboxSession,
198212
onSuccess: OnSuccessHandler?,
199213
onFailure: OnFailureHandler?) -> Pending<SendRequestValue, SendRequestError> {
200-
chooseRequestProcessor().track(inboxSession: inboxSession,
201-
onSuccess: onSuccess,
202-
onFailure: onFailure)
214+
Pending<SendRequestValue, SendRequestError>.inBackgroundThread {
215+
self.chooseRequestProcessor().track(inboxSession: inboxSession,
216+
onSuccess: onSuccess,
217+
onFailure: onFailure)
218+
}
203219
}
204-
220+
205221
@discardableResult
206222
func track(inAppDelivery message: IterableInAppMessage,
207223
onSuccess: OnSuccessHandler?,
208224
onFailure: OnFailureHandler?) -> Pending<SendRequestValue, SendRequestError> {
209-
chooseRequestProcessor().track(inAppDelivery: message,
210-
onSuccess: onSuccess,
211-
onFailure: onFailure)
225+
Pending<SendRequestValue, SendRequestError>.inBackgroundThread {
226+
self.chooseRequestProcessor().track(inAppDelivery: message,
227+
onSuccess: onSuccess,
228+
onFailure: onFailure)
229+
}
212230
}
213231

214232
@discardableResult
215233
func inAppConsume(_ messageId: String,
216234
onSuccess: OnSuccessHandler?,
217235
onFailure: OnFailureHandler?) -> Pending<SendRequestValue, SendRequestError> {
218-
chooseRequestProcessor().inAppConsume(messageId,
219-
onSuccess: onSuccess,
220-
onFailure: onFailure)
236+
Pending<SendRequestValue, SendRequestError>.inBackgroundThread {
237+
self.chooseRequestProcessor().inAppConsume(messageId,
238+
onSuccess: onSuccess,
239+
onFailure: onFailure)
240+
}
221241
}
222242

223243
@discardableResult
@@ -227,12 +247,14 @@ class RequestHandler: RequestHandlerProtocol {
227247
inboxSessionId: String?,
228248
onSuccess: OnSuccessHandler?,
229249
onFailure: OnFailureHandler?) -> Pending<SendRequestValue, SendRequestError> {
230-
chooseRequestProcessor().inAppConsume(message: message,
231-
location: location,
232-
source: source,
233-
inboxSessionId: inboxSessionId,
234-
onSuccess: onSuccess,
235-
onFailure: onFailure)
250+
Pending<SendRequestValue, SendRequestError>.inBackgroundThread {
251+
self.chooseRequestProcessor().inAppConsume(message: message,
252+
location: location,
253+
source: source,
254+
inboxSessionId: inboxSessionId,
255+
onSuccess: onSuccess,
256+
onFailure: onFailure)
257+
}
236258
}
237259

238260
func getRemoteConfiguration() -> Pending<RemoteConfiguration, SendRequestError> {

0 commit comments

Comments
 (0)