Skip to content

Commit a7d29cc

Browse files
Merge pull request #474 from Iterable/tapash/mob-2850-health-monitor
[MOB-2850] - Offline events health monitor
2 parents 3b121ee + 16b8162 commit a7d29cc

24 files changed

+724
-80
lines changed

swift-sdk.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
AC02480822791E2100495FB9 /* IterableInboxNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC02480722791E2100495FB9 /* IterableInboxNavigationViewController.swift */; };
3737
AC02CAA6234E50B5006617E0 /* RegistrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC02CAA5234E50B5006617E0 /* RegistrationTests.swift */; };
3838
AC03094B21E532470003A288 /* InAppPersistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC03094A21E532470003A288 /* InAppPersistence.swift */; };
39+
AC05644B26387B54001FB810 /* MockPersistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC05644A26387B54001FB810 /* MockPersistence.swift */; };
3940
AC0674E620D8766600C2806D /* NotificationExtensionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AC0674E520D8766600C2806D /* NotificationExtensionTests.m */; };
4041
AC1670CD2230A91C00989F8E /* InboxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC1670CC2230A91C00989F8E /* InboxTests.swift */; };
4142
AC1712892416AEF400F2BB0E /* WebViewProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC1712882416AEF400F2BB0E /* WebViewProtocol.swift */; };
@@ -160,6 +161,8 @@
160161
ACA8D1A921965B7D001B1332 /* InAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACA8D1A821965B7D001B1332 /* InAppTests.swift */; };
161162
ACA8D1AB21966555001B1332 /* InAppManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACA8D1AA21966555001B1332 /* InAppManager.swift */; };
162163
ACAA816E231163660035C743 /* RequestCreatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACAA816D231163660035C743 /* RequestCreatorTests.swift */; };
164+
ACB1DFD126369CC300A31597 /* HealthMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACB1DFD026369CC300A31597 /* HealthMonitorTests.swift */; };
165+
ACB1DFDB26369D2F00A31597 /* HealthMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACB1DFDA26369D2F00A31597 /* HealthMonitor.swift */; };
163166
ACB37AB0240268A60093A8EA /* SampleInboxViewDelegateImplementations.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACB37AAF240268A60093A8EA /* SampleInboxViewDelegateImplementations.swift */; };
164167
ACB37AB124026C1E0093A8EA /* SampleInboxViewDelegateImplementations.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACB37AAF240268A60093A8EA /* SampleInboxViewDelegateImplementations.swift */; };
165168
ACB8273F22372A5C00DB17D3 /* IterableHtmlMessageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACB8273E22372A5C00DB17D3 /* IterableHtmlMessageViewController.swift */; };
@@ -387,6 +390,7 @@
387390
AC02480722791E2100495FB9 /* IterableInboxNavigationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableInboxNavigationViewController.swift; sourceTree = "<group>"; };
388391
AC02CAA5234E50B5006617E0 /* RegistrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationTests.swift; sourceTree = "<group>"; };
389392
AC03094A21E532470003A288 /* InAppPersistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPersistence.swift; sourceTree = "<group>"; };
393+
AC05644A26387B54001FB810 /* MockPersistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPersistence.swift; sourceTree = "<group>"; };
390394
AC0674E420D8766600C2806D /* notification-extensionTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "notification-extensionTests-Bridging-Header.h"; sourceTree = "<group>"; };
391395
AC0674E520D8766600C2806D /* NotificationExtensionTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotificationExtensionTests.m; sourceTree = "<group>"; };
392396
AC0A45372179300D0040394F /* host-app.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "host-app.entitlements"; sourceTree = "<group>"; };
@@ -506,6 +510,8 @@
506510
ACA8D1A821965B7D001B1332 /* InAppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppTests.swift; sourceTree = "<group>"; };
507511
ACA8D1AA21966555001B1332 /* InAppManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppManager.swift; sourceTree = "<group>"; };
508512
ACAA816D231163660035C743 /* RequestCreatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestCreatorTests.swift; sourceTree = "<group>"; };
513+
ACB1DFD026369CC300A31597 /* HealthMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthMonitorTests.swift; sourceTree = "<group>"; };
514+
ACB1DFDA26369D2F00A31597 /* HealthMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthMonitor.swift; sourceTree = "<group>"; };
509515
ACB37AAF240268A60093A8EA /* SampleInboxViewDelegateImplementations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleInboxViewDelegateImplementations.swift; sourceTree = "<group>"; };
510516
ACB8273E22372A5C00DB17D3 /* IterableHtmlMessageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableHtmlMessageViewController.swift; sourceTree = "<group>"; };
511517
ACBDDE5B23C4EDEC0008CC4D /* InboxCustomizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InboxCustomizationTests.swift; sourceTree = "<group>"; };
@@ -939,6 +945,7 @@
939945
AC8E7CA424C7555E0039605F /* CoreDataUtil.swift */,
940946
ACFD5AB224C8179D008E497A /* PersistenceHelper.swift */,
941947
ACFD5AC724C8290E008E497A /* IterableTaskManagedObject.swift */,
948+
ACB1DFDA26369D2F00A31597 /* HealthMonitor.swift */,
942949
);
943950
name = Persistence;
944951
sourceTree = "<group>";
@@ -1251,6 +1258,8 @@
12511258
AC2AED4124EBC60C000EE5F3 /* TaskRunnerTests.swift */,
12521259
AC3EFFEF2510B8FB007F1330 /* TaskSchedulerTests.swift */,
12531260
ACFD5AC524C8216A008E497A /* TasksCRUDTests.swift */,
1261+
ACB1DFD026369CC300A31597 /* HealthMonitorTests.swift */,
1262+
AC05644A26387B54001FB810 /* MockPersistence.swift */,
12541263
);
12551264
path = "offline-events-tests";
12561265
sourceTree = "<group>";
@@ -1731,6 +1740,7 @@
17311740
ACC3FDB1253724DB0004A2E0 /* InAppCalculations.swift in Sources */,
17321741
ACD6116C2107D004003E7F6B /* NetworkHelper.swift in Sources */,
17331742
ACC362B624D16D91002C67BA /* IterableRequest.swift in Sources */,
1743+
ACB1DFDB26369D2F00A31597 /* HealthMonitor.swift in Sources */,
17341744
ACC362BD24D21172002C67BA /* IterableAPICallTaskProcessor.swift in Sources */,
17351745
AC84510922910A0C0052BB8F /* RequestCreator.swift in Sources */,
17361746
ACB8273F22372A5C00DB17D3 /* IterableHtmlMessageViewController.swift in Sources */,
@@ -1942,11 +1952,13 @@
19421952
isa = PBXSourcesBuildPhase;
19431953
buildActionMask = 2147483647;
19441954
files = (
1955+
ACB1DFD126369CC300A31597 /* HealthMonitorTests.swift in Sources */,
19451956
AC3EFFF02510B8FB007F1330 /* TaskSchedulerTests.swift in Sources */,
19461957
AC67AF982507481200C1E974 /* NetworkConnectivityCheckerTests.swift in Sources */,
19471958
ACFD5AC624C8216A008E497A /* TasksCRUDTests.swift in Sources */,
19481959
ACC362C624D2C334002C67BA /* CommonExtensions.swift in Sources */,
19491960
ACF406252507F90F005FD775 /* NetworkConnectivityManagerTests.swift in Sources */,
1961+
AC05644B26387B54001FB810 /* MockPersistence.swift in Sources */,
19501962
ACCF274C24F40C85004862D5 /* RequestHandlerTests.swift in Sources */,
19511963
ACC362C724D2C647002C67BA /* CommonMocks.swift in Sources */,
19521964
ACC362C824D2C7C9002C67BA /* TestUtils.swift in Sources */,

swift-sdk/Internal/ApiClient.swift

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,8 @@ class ApiClient {
3636
endPoint: endPoint,
3737
auth: authProvider.auth,
3838
deviceMetadata: deviceMetadata,
39-
iterableRequest: iterableRequest)
40-
.addingBodyField(key: JsonKey.Body.createdAt,
41-
value: IterableUtil.secondsFromEpoch(for: currentDate))
42-
return apiCallRequest.convertToURLRequest(currentDate: currentDate)
39+
iterableRequest: iterableRequest).addingCreatedAt(currentDate)
40+
return apiCallRequest.convertToURLRequest(sentAt: currentDate)
4341
}
4442

4543
func send(iterableRequestResult result: Result<IterableRequest, IterableError>) -> Future<SendRequestValue, SendRequestError> {

swift-sdk/Internal/CoreDataUtil.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@ struct CoreDataUtil {
2424
let request = NSFetchRequest<T>(entityName: entity)
2525
return try context.fetch(request)
2626
}
27-
27+
28+
static func count(context: NSManagedObjectContext, entity: String) throws -> Int {
29+
let request = NSFetchRequest<NSFetchRequestResult>(entityName: entity)
30+
return try context.count(for: request)
31+
}
32+
2833
static func findEntitiesByColumns<T: NSFetchRequestResult>(context: NSManagedObjectContext, entity: String, columns: [String: Any]) throws -> [T] {
2934
let request = NSFetchRequest<T>(entityName: entity)
3035
request.predicate = createColumnsPredicate(columns: columns)

swift-sdk/Internal/DependencyContainer.swift

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ protocol DependencyContainerProtocol {
2727
authManager: IterableInternalAuthManagerProtocol,
2828
deviceMetadata: DeviceMetadata,
2929
offlineMode: Bool) -> RequestHandlerProtocol
30+
func createHealthMonitorDataProvider(persistenceContextProvider: IterablePersistenceContextProvider) -> HealthMonitorDataProviderProtocol
3031
}
3132

3233
extension DependencyContainerProtocol {
@@ -71,20 +72,31 @@ extension DependencyContainerProtocol {
7172
networkSession: networkSession,
7273
deviceMetadata: deviceMetadata,
7374
dateProvider: dateProvider)
74-
let offlineProcessor: OfflineRequestProcessor?
7575
if let persistenceContextProvider = createPersistenceContextProvider() {
76-
offlineProcessor = OfflineRequestProcessor(apiKey: apiKey,
77-
authProvider: authProvider,
78-
authManager: authManager,
79-
endPoint: endPoint,
80-
deviceMetadata: deviceMetadata,
81-
taskScheduler: createTaskScheduler(persistenceContextProvider: persistenceContextProvider),
82-
taskRunner: createTaskRunner(persistenceContextProvider: persistenceContextProvider),
83-
notificationCenter: notificationCenter)
76+
let healthMonitorDataProvider = createHealthMonitorDataProvider(persistenceContextProvider: persistenceContextProvider)
77+
let healthMonitor = HealthMonitor(dataProvider: healthMonitorDataProvider,
78+
dateProvider: dateProvider,
79+
networkSession: networkSession)
80+
let offlineProcessor = OfflineRequestProcessor(apiKey: apiKey,
81+
authProvider: authProvider,
82+
authManager: authManager,
83+
endPoint: endPoint,
84+
deviceMetadata: deviceMetadata,
85+
taskScheduler: createTaskScheduler(persistenceContextProvider: persistenceContextProvider,
86+
healthMonitor: healthMonitor),
87+
taskRunner: createTaskRunner(persistenceContextProvider: persistenceContextProvider,
88+
healthMonitor: healthMonitor),
89+
notificationCenter: notificationCenter)
90+
return RequestHandler(onlineProcessor: onlineProcessor,
91+
offlineProcessor: offlineProcessor,
92+
healthMonitor: healthMonitor,
93+
offlineMode: offlineMode)
8494
} else {
85-
offlineProcessor = nil
95+
return RequestHandler(onlineProcessor: onlineProcessor,
96+
offlineProcessor: nil,
97+
healthMonitor: nil,
98+
offlineMode: offlineMode)
8699
}
87-
return RequestHandler(onlineProcessor: onlineProcessor, offlineProcessor: offlineProcessor, offlineMode: offlineMode)
88100
} else {
89101
return LegacyRequestHandler(apiKey: apiKey,
90102
authProvider: authProvider,
@@ -96,6 +108,10 @@ extension DependencyContainerProtocol {
96108
}
97109
}
98110

111+
func createHealthMonitorDataProvider(persistenceContextProvider: IterablePersistenceContextProvider) -> HealthMonitorDataProviderProtocol {
112+
HealthMonitorDataProvider(maxTasks: 1000, persistenceContextProvider: persistenceContextProvider)
113+
}
114+
99115
func createPersistenceContextProvider() -> IterablePersistenceContextProvider? {
100116
if #available(iOS 10.0, *) {
101117
return CoreDataPersistenceContextProvider(dateProvider: dateProvider)
@@ -105,16 +121,20 @@ extension DependencyContainerProtocol {
105121
}
106122

107123
@available(iOS 10.0, *)
108-
private func createTaskScheduler(persistenceContextProvider: IterablePersistenceContextProvider) -> IterableTaskScheduler {
124+
private func createTaskScheduler(persistenceContextProvider: IterablePersistenceContextProvider,
125+
healthMonitor: HealthMonitor) -> IterableTaskScheduler {
109126
IterableTaskScheduler(persistenceContextProvider: persistenceContextProvider,
110127
notificationCenter: notificationCenter,
128+
healthMonitor: healthMonitor,
111129
dateProvider: dateProvider)
112130
}
113131

114132
@available(iOS 10.0, *)
115-
private func createTaskRunner(persistenceContextProvider: IterablePersistenceContextProvider) -> IterableTaskRunner {
133+
private func createTaskRunner(persistenceContextProvider: IterablePersistenceContextProvider,
134+
healthMonitor: HealthMonitor) -> IterableTaskRunner {
116135
IterableTaskRunner(networkSession: networkSession,
117136
persistenceContextProvider: persistenceContextProvider,
137+
healthMonitor: healthMonitor,
118138
notificationCenter: notificationCenter,
119139
connectivityManager: NetworkConnectivityManager())
120140
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
//
2+
// Copyright © 2021 Iterable. All rights reserved.
3+
//
4+
5+
import Foundation
6+
7+
protocol HealthMonitorDataProviderProtocol {
8+
var maxTasks: Int { get }
9+
func countTasks() throws -> Int
10+
}
11+
12+
struct HealthMonitorDataProvider: HealthMonitorDataProviderProtocol {
13+
init(maxTasks: Int,
14+
persistenceContextProvider: IterablePersistenceContextProvider) {
15+
self.maxTasks = maxTasks
16+
self.persistenceContextProvider = persistenceContextProvider
17+
}
18+
19+
let maxTasks: Int
20+
21+
func countTasks() throws -> Int {
22+
return try persistenceContextProvider.newBackgroundContext().countTasks()
23+
}
24+
25+
private let persistenceContextProvider: IterablePersistenceContextProvider
26+
}
27+
28+
protocol HealthMonitorDelegate: AnyObject {
29+
func onDBError()
30+
}
31+
32+
class HealthMonitor {
33+
init(dataProvider: HealthMonitorDataProviderProtocol,
34+
dateProvider: DateProviderProtocol,
35+
networkSession: NetworkSessionProtocol) {
36+
ITBInfo()
37+
self.dataProvider = dataProvider
38+
self.dateProvider = dateProvider
39+
self.networkSession = networkSession
40+
}
41+
42+
deinit {
43+
ITBInfo()
44+
}
45+
46+
weak var delegate: HealthMonitorDelegate?
47+
48+
func canSchedule() -> Bool {
49+
ITBInfo()
50+
// do not schedule further on error
51+
guard errored == false else {
52+
return false
53+
}
54+
55+
do {
56+
let count = try dataProvider.countTasks()
57+
return count < dataProvider.maxTasks
58+
} catch let error {
59+
ITBError("DBError: " + error.localizedDescription)
60+
onError()
61+
return false
62+
}
63+
}
64+
65+
func canProcess() -> Bool {
66+
ITBInfo()
67+
return !errored
68+
}
69+
70+
func onScheduleError(apiCallRequest: IterableAPICallRequest) {
71+
ITBInfo()
72+
let currentDate = dateProvider.currentDate
73+
let apiCallRequest = apiCallRequest.addingCreatedAt(currentDate)
74+
if let urlRequest = apiCallRequest.convertToURLRequest(sentAt: currentDate) {
75+
_ = RequestSender.sendRequest(urlRequest, usingSession: networkSession)
76+
}
77+
onError()
78+
}
79+
80+
func onDeleteError(task: IterableTask) {
81+
ITBInfo()
82+
onError()
83+
}
84+
85+
func onNextTaskError() {
86+
ITBInfo()
87+
onError()
88+
}
89+
90+
func onDeleteAllTasksError() {
91+
ITBInfo()
92+
onError()
93+
}
94+
95+
private func onError() {
96+
errored = true
97+
delegate?.onDBError()
98+
}
99+
100+
private var errored = false
101+
private let dataProvider: HealthMonitorDataProviderProtocol
102+
private let dateProvider: DateProviderProtocol
103+
private let networkSession: NetworkSessionProtocol
104+
}

swift-sdk/Internal/InternalIterableAPI.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ final class InternalIterableAPI: NSObject, PushTrackerProtocol, AuthProvider {
393393
dateProvider: dateProvider)
394394
}()
395395

396-
private lazy var requestHandler: RequestHandlerProtocol = {
396+
lazy var requestHandler: RequestHandlerProtocol = {
397397
let offlineMode = self.localStorage.isOfflineModeEnabled()
398398
return dependencyContainer.createRequestHandler(apiKey: apiKey,
399399
config: config,

0 commit comments

Comments
 (0)