Skip to content

Commit 614c816

Browse files
authored
Merge pull request #19395 from wordpress-mobile/reimplement-notification-cache-observer
Reimplement invaliding `Notification` attributes cache
2 parents dfe3ff5 + da753bd commit 614c816

File tree

2 files changed

+75
-21
lines changed

2 files changed

+75
-21
lines changed

WordPress/Classes/Models/Notifications/Notification.swift

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,32 @@ class Notification: NSManagedObject {
8181
///
8282
fileprivate var cachedHeaderAndBodyContentGroups: [FormattableContentGroup]?
8383

84+
private var cachedAttributesObserver: NotificationCachedAttributesObserver?
85+
8486
/// Array that contains the Cached Property Names
8587
///
8688
fileprivate static let cachedAttributes = Set(arrayLiteral: "body", "header", "subject", "timestamp")
8789

90+
override func awakeFromFetch() {
91+
super.awakeFromFetch()
92+
93+
if cachedAttributesObserver == nil {
94+
let observer = NotificationCachedAttributesObserver()
95+
for attr in Notification.cachedAttributes {
96+
addObserver(observer, forKeyPath: attr, options: [.prior], context: nil)
97+
}
98+
cachedAttributesObserver = observer
99+
}
100+
}
101+
102+
deinit {
103+
if let observer = cachedAttributesObserver {
104+
for attr in Notification.cachedAttributes {
105+
removeObserver(observer, forKeyPath: attr)
106+
}
107+
}
108+
}
109+
88110
func renderSubject() -> NSAttributedString? {
89111
guard let subjectContent = subjectContentGroup?.blocks.first else {
90112
return nil
@@ -99,26 +121,6 @@ class Notification: NSManagedObject {
99121
return formatter.render(content: snippetContent, with: SnippetsContentStyles())
100122
}
101123

102-
/// When needed, nukes cached attributes
103-
///
104-
override func willChangeValue(forKey key: String) {
105-
super.willChangeValue(forKey: key)
106-
107-
// Note:
108-
// Cached Attributes are only consumed on the main thread, when initializing UI elements.
109-
// As an optimization, we'll only reset those attributes when we're running on the main thread.
110-
//
111-
guard managedObjectContext?.concurrencyType == .mainQueueConcurrencyType else {
112-
return
113-
}
114-
115-
guard Swift.type(of: self).cachedAttributes.contains(key) else {
116-
return
117-
}
118-
119-
resetCachedAttributes()
120-
}
121-
122124
/// Nukes any cached values.
123125
///
124126
func resetCachedAttributes() {
@@ -412,3 +414,25 @@ extension Notification: Notifiable {
412414
return notificationId
413415
}
414416
}
417+
418+
private class NotificationCachedAttributesObserver: NSObject {
419+
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
420+
guard let keyPath, let notification = object as? Notification, Notification.cachedAttributes.contains(keyPath) else {
421+
return
422+
}
423+
424+
guard (change?[.notificationIsPriorKey] as? NSNumber)?.boolValue == true else {
425+
return
426+
}
427+
428+
// Note:
429+
// Cached Attributes are only consumed on the main thread, when initializing UI elements.
430+
// As an optimization, we'll only reset those attributes when we're running on the main thread.
431+
//
432+
guard notification.managedObjectContext?.concurrencyType == .mainQueueConcurrencyType else {
433+
return
434+
}
435+
436+
notification.resetCachedAttributes()
437+
}
438+
}

WordPress/WordPressTest/NotificationTests.swift

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,37 @@ class NotificationTests: CoreDataTestCase {
229229
XCTAssertEqual(note.headerAndBodyContentGroups.count, totalGroupsCount)
230230
}
231231

232-
232+
func testNotificationCacheIsInvalidated() throws {
233+
let commentNotificationId = "44444"
234+
let _ = try utility.loadCommentNotification()
235+
contextManager.saveContextAndWait(mainContext)
236+
237+
let fetchRequest = NSFetchRequest<WordPress.Notification>(entityName: WordPress.Notification.entityName())
238+
fetchRequest.predicate = NSPredicate(format: "notificationId == %@", commentNotificationId)
239+
240+
let note = try XCTUnwrap(mainContext.fetch(fetchRequest).first)
241+
XCTAssertEqual(note.timestamp, "2015-03-10T18:57:37+00:00")
242+
XCTAssertEqual(note.timestampAsDate.timeIntervalSince1970, 1426013857)
243+
244+
note.timestamp = "2015-03-10T18:57:38+00:00"
245+
XCTAssertEqual(note.timestampAsDate.timeIntervalSince1970, 1426013858)
246+
247+
mainContext.reset()
248+
249+
contextManager.performAndSave { context in
250+
let notification = (try? context.fetch(fetchRequest))?.first
251+
XCTAssertNotNil(notification)
252+
XCTAssertEqual(notification?.timestampAsDate.timeIntervalSince1970, 1426013857)
253+
254+
XCTExpectFailure("""
255+
This assertion failure is problematic.
256+
When timestamp (or any other cached attribute) is changed, the cache still should be invalidated
257+
even though the notification is associated with a non-main context.
258+
""")
259+
note.timestamp = "2015-03-10T18:57:38+00:00"
260+
XCTAssertEqual(notification?.timestampAsDate.timeIntervalSince1970, 1426013858)
261+
}
262+
}
233263

234264
// MARK: - Helpers
235265

0 commit comments

Comments
 (0)