Skip to content

Commit 04b361f

Browse files
committed
MBX-3712: Solution for Extensions, fix fetch and new tests
1 parent 8d20b3d commit 04b361f

File tree

4 files changed

+154
-128
lines changed

4 files changed

+154
-128
lines changed

MindboxLogger/Shared/LoggerRepository/MBLoggerCoreDataManager.swift

Lines changed: 82 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public class MBLoggerCoreDataManager {
1717
static let model = "CDLogMessage"
1818
static let dbSizeLimitKB: Int = 10_000
1919
static let batchSize = 15
20-
20+
2121
/// batch size | operationBatchLimitBeforeNeedToDelete
2222
/// 1 | 20
2323
/// 2 | 14
@@ -36,9 +36,11 @@ public class MBLoggerCoreDataManager {
3636
return max(1, Int(20 / pow(Double(batchSize), 0.5)))
3737
}()
3838
}
39-
40-
private var logBuffer: [LogMessage] = []
41-
private let queue = DispatchQueue(label: "com.Mindbox.loggerManager", qos: .utility)
39+
40+
private var logBuffer: [LogMessage]
41+
private let isAppExtension: Bool
42+
private let queue: DispatchQueue
43+
4244
private var writeCount = 0 {
4345
didSet {
4446
if writeCount > Constants.operationBatchLimitBeforeNeedToDelete {
@@ -47,7 +49,22 @@ public class MBLoggerCoreDataManager {
4749
}
4850
}
4951
}
50-
52+
53+
private lazy var flushStrategy: () -> Void = {
54+
if isAppExtension {
55+
return { [weak self] in
56+
self?.flushBuffer()
57+
}
58+
} else {
59+
return { [weak self] in
60+
guard let self = self else { return }
61+
if self.logBuffer.count >= Constants.batchSize {
62+
self.flushBuffer()
63+
}
64+
}
65+
}
66+
}()
67+
5168
// MARK: CoreData objects
5269

5370
private lazy var persistentContainer: MBPersistentContainer = {
@@ -93,51 +110,53 @@ public class MBLoggerCoreDataManager {
93110
context.mergePolicy = NSMergePolicy(merge: .mergeByPropertyStoreTrumpMergePolicyType)
94111
return context
95112
}()
96-
113+
97114
// MARK: Initializers and deinitializer
98-
99-
private init() {
115+
116+
private init(isAppExtension: Bool = Bundle.main.bundlePath.hasSuffix(".appex")) {
117+
self.isAppExtension = isAppExtension
118+
self.logBuffer = []
119+
self.logBuffer.reserveCapacity(Constants.batchSize)
120+
self.queue = DispatchQueue(label: "com.Mindbox.loggerManager", qos: .utility)
100121
setupNotificationCenterObservers()
101122
}
102-
123+
103124
deinit {
104-
removeAllNotificationCenterObservers()
125+
NotificationCenter.default.removeObserver(self)
105126
}
106127
}
107128

108129
// MARK: - CRUD Operations
109130

110131
public extension MBLoggerCoreDataManager {
111132
// MARK: Create
112-
133+
113134
func create(message: String, timestamp: Date, completion: (() -> Void)? = nil) {
114135
queue.async {
115136
let newLogMessage = LogMessage(timestamp: timestamp, message: message)
116137
self.logBuffer.append(newLogMessage)
117-
118-
if self.logBuffer.count >= Constants.batchSize {
119-
self.flushBuffer()
120-
}
121-
138+
139+
self.flushStrategy()
140+
122141
completion?()
123142
}
124143
}
125-
144+
126145
private func flushBuffer() {
127146
guard !logBuffer.isEmpty else { return }
128-
147+
129148
if #available(iOS 13.0, *) {
130149
performBatchInsert()
131150
} else {
132151
performContextInsertion()
133152
}
134153
}
135-
154+
136155
@available(iOS 13.0, *)
137156
private func performBatchInsert() {
138157
let insertData = logBuffer.map { ["message": $0.message, "timestamp": $0.timestamp] }
139158
let insertRequest = NSBatchInsertRequest(entityName: Constants.model, objects: insertData)
140-
159+
141160
do {
142161
try context.execute(insertRequest)
143162
logBuffer.removeAll()
@@ -146,23 +165,23 @@ public extension MBLoggerCoreDataManager {
146165
let errorMessage = "Failed to batch insert logs: \(error.localizedDescription)"
147166
let errorLogData = [["message": errorMessage, "timestamp": Date()]]
148167
let errorLogInsertRequest = NSBatchInsertRequest(entityName: Constants.model, objects: errorLogData)
149-
168+
150169
do {
151170
try context.execute(errorLogInsertRequest)
152171
} catch { }
153172
}
154173
}
155-
174+
156175
// MARK: Read
157-
176+
158177
func getFirstLog() throws -> LogMessage? {
159178
return try fetchSingleLog(ascending: true)
160179
}
161180

162181
func getLastLog() throws -> LogMessage? {
163182
return try fetchSingleLog(ascending: false)
164183
}
165-
184+
166185
private func fetchSingleLog(ascending: Bool) throws -> LogMessage? {
167186
var fetchedLogMessage: LogMessage?
168187
try context.executePerformAndWait {
@@ -175,58 +194,66 @@ public extension MBLoggerCoreDataManager {
175194
}
176195
return fetchedLogMessage
177196
}
178-
179-
func fetchPeriod(_ from: Date, _ to: Date) throws -> [LogMessage] {
197+
198+
func fetchPeriod(_ from: Date, _ to: Date, ascending: Bool = true) throws -> [LogMessage] {
180199
var fetchedLogs: [LogMessage] = []
181-
200+
182201
try context.executePerformAndWait {
183-
let fetchRequest = NSFetchRequest<CDLogMessage>(entityName: Constants.model)
202+
let fetchRequest = NSFetchRequest<NSDictionary>(entityName: Constants.model)
203+
fetchRequest.resultType = .dictionaryResultType
204+
fetchRequest.propertiesToFetch = ["timestamp", "message", "objectID"]
184205
fetchRequest.predicate = NSPredicate(format: "timestamp >= %@ AND timestamp <= %@",
185206
from as NSDate, to as NSDate)
186-
187207
fetchRequest.fetchBatchSize = 50 // Setting batchSize for optimal memory consumption
188-
189-
let logs = try context.fetch(fetchRequest)
190-
fetchedLogs = logs.map { LogMessage(timestamp: $0.timestamp, message: $0.message) }
208+
209+
let sortDescriptor = NSSortDescriptor(key: "timestamp", ascending: ascending)
210+
fetchRequest.sortDescriptors = [sortDescriptor]
211+
212+
let results = try context.fetch(fetchRequest)
213+
fetchedLogs = results.compactMap { dict -> LogMessage? in
214+
guard let timestamp = dict["timestamp"] as? Date,
215+
let message = dict["message"] as? String else { return nil }
216+
return LogMessage(timestamp: timestamp, message: message)
217+
}
191218
}
192-
219+
193220
return fetchedLogs
194221
}
195-
222+
196223
// MARK: Delete
197-
224+
198225
func deleteTenPercentOfAllOldRecords() throws {
199226
try context.executePerformAndWait {
200227
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: Constants.model)
201-
228+
202229
let count = try context.count(for: fetchRequest)
203230
let limit = Int((Double(count) * 0.1).rounded()) // 10% percent of all records should be removed
204-
231+
205232
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: true)]
206233
fetchRequest.fetchLimit = limit
207-
234+
208235
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
209236
deleteRequest.resultType = .resultTypeObjectIDs
210-
237+
211238
let batchDeleteResult = try context.execute(deleteRequest) as? NSBatchDeleteResult
212239
if let objectIDs = batchDeleteResult?.result as? [NSManagedObjectID] {
213240
let changes = [NSDeletedObjectsKey: objectIDs]
214241
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [context])
215242
}
216-
243+
217244
Logger.common(message: "\(limit) logs have been deleted", level: .debug, category: .general)
218245
}
219246
}
220-
247+
221248
func deleteAll() throws {
222249
self.logBuffer.removeAll()
223250
try context.executePerformAndWait {
224251
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: Constants.model)
225252
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
226253
deleteRequest.resultType = .resultTypeObjectIDs
227-
254+
228255
let batchDeleteResult = try context.execute(deleteRequest) as? NSBatchDeleteResult
229-
256+
230257
if let objectIDs = batchDeleteResult?.result as? [NSManagedObjectID] {
231258
let changes: [AnyHashable: Any] = [NSDeletedObjectsKey: objectIDs]
232259
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [context])
@@ -243,24 +270,20 @@ private extension MBLoggerCoreDataManager {
243270
selector: #selector(applicationDidEnterBackground),
244271
name: UIApplication.didEnterBackgroundNotification,
245272
object: nil)
246-
273+
247274
NotificationCenter.default.addObserver(self,
248275
selector: #selector(applicationWillTerminate),
249276
name: UIApplication.willTerminateNotification,
250277
object: nil)
251278
}
252-
253-
func removeAllNotificationCenterObservers() {
254-
NotificationCenter.default.removeObserver(self)
255-
}
256-
279+
257280
@objc
258281
func applicationDidEnterBackground() {
259282
queue.async { [weak self] in
260283
self?.flushBuffer()
261284
}
262285
}
263-
286+
264287
@objc
265288
func applicationWillTerminate() {
266289
queue.async { [weak self] in
@@ -280,7 +303,7 @@ private extension MBLoggerCoreDataManager {
280303
entity.message = log.message
281304
entity.timestamp = log.timestamp
282305
}
283-
306+
284307
try self.saveEvent(withContext: self.context)
285308
logBuffer.removeAll()
286309
writeCount += 1
@@ -290,13 +313,13 @@ private extension MBLoggerCoreDataManager {
290313
let errorLogEntity = CDLogMessage(context: self.context)
291314
errorLogEntity.message = errorMessage
292315
errorLogEntity.timestamp = Date()
293-
316+
294317
do {
295318
try self.saveEvent(withContext: self.context)
296319
} catch { }
297320
}
298321
}
299-
322+
300323
func saveEvent(withContext context: NSManagedObjectContext) throws {
301324
guard context.hasChanges else { return }
302325
try saveContext(context)
@@ -327,7 +350,7 @@ private extension MBLoggerCoreDataManager {
327350
} catch { }
328351
}
329352
}
330-
353+
331354
func getDBFileSize() -> Int {
332355
guard let url = context.persistentStoreCoordinator?.persistentStores.first?.url else {
333356
return 0
@@ -342,11 +365,15 @@ extension MBLoggerCoreDataManager {
342365
var debugBatchSize: Int {
343366
return Constants.batchSize
344367
}
345-
368+
346369
func debugFlushBuffer() {
347370
queue.async {
348371
self.flushBuffer()
349372
}
350373
}
374+
375+
convenience init(debugIsAppExtension: Bool) {
376+
self.init(isAppExtension: debugIsAppExtension)
377+
}
351378
}
352379
#endif

MindboxTests/MigrationsTests/ShownInAppsIdsMigrationTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ final class ShownInAppsIdsMigrationTests: XCTestCase {
6464
let migrationExpectation = XCTestExpectation(description: "Migration completed")
6565
migrationManager.migrate()
6666
mbLoggerCDManager.debugFlushBuffer()
67-
67+
6868
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
6969
XCTAssertNotNil(self.persistenceStorageMock.shownInappsDictionary, "shownInAppDictionary must NOT be nil after MigrationShownInAppIds")
7070
XCTAssertNil(self.persistenceStorageMock.shownInAppsIds, "shownInAppsIds must be nil after MigrationShownInAppIds")
@@ -136,7 +136,7 @@ final class ShownInAppsIdsMigrationTests: XCTestCase {
136136

137137
migrationManager.migrate()
138138
mbLoggerCDManager.debugFlushBuffer()
139-
139+
140140
let migrationExpectation = XCTestExpectation(description: "Migration completed")
141141
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
142142
migrationExpectation.fulfill()

0 commit comments

Comments
 (0)