@@ -95,7 +95,9 @@ public final class Storage {
9595 return
9696 }
9797
98- performSerializedBackgroundWrite ( writeBlock: { backgroundRealm in
98+ performSerializedBackgroundWrite ( disableAutorefresh: true , completionBlock: completion) { backgroundRealm in
99+ var time = Date ( )
100+ print ( " Starting sessions: \( ( Date ( ) . timeIntervalSince1970 - time. timeIntervalSince1970) . formatted ( . number. precision ( . fractionLength( 2 ) ) ) ) " )
99101 contentsResponse. sessions. forEach { newSession in
100102 // Replace any "unknown" resources with their full data
101103 newSession. related. filter ( { $0. type == RelatedResourceType . unknown. rawValue} ) . forEach { unknownResource in
@@ -111,7 +113,11 @@ public final class Storage {
111113 backgroundRealm. add ( newSession, update: . all)
112114 }
113115 }
116+ print ( " Ending sessions: \( ( Date ( ) . timeIntervalSince1970 - time. timeIntervalSince1970) . formatted ( . number. precision ( . fractionLength( 2 ) ) ) ) " )
114117
118+ time = Date ( )
119+ // TODO: Takes 8+ seconds, several notable opportunities to optimize storage accesses
120+ print ( " Starting session instances: \( ( Date ( ) . timeIntervalSince1970 - time. timeIntervalSince1970) . formatted ( . number. precision ( . fractionLength( 2 ) ) ) ) " )
115121 // Merge existing instance data, preserving user-defined data
116122 contentsResponse. instances. forEach { newInstance in
117123 if let existingInstance = backgroundRealm. object ( ofType: SessionInstance . self, forPrimaryKey: newInstance. identifier) {
@@ -128,13 +134,19 @@ public final class Storage {
128134 backgroundRealm. add ( newInstance, update: . all)
129135 }
130136 }
137+ print ( " Ending session instances: \( ( Date ( ) . timeIntervalSince1970 - time. timeIntervalSince1970) . formatted ( . number. precision ( . fractionLength( 2 ) ) ) ) " )
131138
132139 // Save everything
140+ time = Date ( )
141+ print ( " Starting save everything: \( ( Date ( ) . timeIntervalSince1970 - time. timeIntervalSince1970) . formatted ( . number. precision ( . fractionLength( 2 ) ) ) ) " )
133142 backgroundRealm. add ( contentsResponse. rooms, update: . all)
134143 backgroundRealm. add ( contentsResponse. tracks, update: . all)
135144 backgroundRealm. add ( contentsResponse. events, update: . all)
145+ print ( " Ending save everything: \( ( Date ( ) . timeIntervalSince1970 - time. timeIntervalSince1970) . formatted ( . number. precision ( . fractionLength( 2 ) ) ) ) " )
136146
137147 // add instances to rooms
148+ time = Date ( )
149+ print ( " Starting add instances to room: \( ( Date ( ) . timeIntervalSince1970 - time. timeIntervalSince1970) . formatted ( . number. precision ( . fractionLength( 2 ) ) ) ) " )
138150 backgroundRealm. objects ( Room . self) . forEach { room in
139151 let instances = backgroundRealm. objects ( SessionInstance . self) . filter ( " roomIdentifier == %@ " , room. identifier)
140152
@@ -143,8 +155,12 @@ public final class Storage {
143155 room. instances. removeAll ( )
144156 room. instances. append ( objectsIn: instances)
145157 }
158+ print ( " Ending add instances to room: \( ( Date ( ) . timeIntervalSince1970 - time. timeIntervalSince1970) . formatted ( . number. precision ( . fractionLength( 2 ) ) ) ) " )
146159
147160 // add instances and sessions to events
161+ // TODO: takes 0.4 seconds, could these List's become LinkingObjects so we don't have to store them and then pull them back out?
162+ time = Date ( )
163+ print ( " Starting add instances and sessions to events: \( ( Date ( ) . timeIntervalSince1970 - time. timeIntervalSince1970) . formatted ( . number. precision ( . fractionLength( 2 ) ) ) ) " )
148164 backgroundRealm. objects ( Event . self) . forEach { event in
149165 let instances = backgroundRealm. objects ( SessionInstance . self) . filter ( " eventIdentifier == %@ " , event. identifier)
150166 let sessions = backgroundRealm. objects ( Session . self) . filter ( " eventIdentifier == %@ " , event. identifier)
@@ -155,8 +171,12 @@ public final class Storage {
155171 event. sessions. removeAll ( )
156172 event. sessions. append ( objectsIn: sessions)
157173 }
174+ print ( " Ending add instances and sessions to events: \( ( Date ( ) . timeIntervalSince1970 - time. timeIntervalSince1970) . formatted ( . number. precision ( . fractionLength( 2 ) ) ) ) " )
158175
159176 // add instances and sessions to tracks
177+ time = Date ( )
178+ print ( " Starting add instances and sessions to tracks: \( ( Date ( ) . timeIntervalSince1970 - time. timeIntervalSince1970) . formatted ( . number. precision ( . fractionLength( 2 ) ) ) ) " )
179+ // TODO: takes 1.5 seconds, could these List's become LinkingObjects so we don't have to store them and then pull them back out?
160180 backgroundRealm. objects ( Track . self) . forEach { track in
161181 let instances = backgroundRealm. objects ( SessionInstance . self) . filter ( " trackIdentifier == %@ " , track. identifier)
162182 let sessions = backgroundRealm. objects ( Session . self) . filter ( " trackIdentifier == %@ " , track. identifier)
@@ -173,54 +193,59 @@ public final class Storage {
173193 instance. session? . trackName = track. name
174194 }
175195 }
196+ print ( " Ending add instances and sessions to tracks: \( ( Date ( ) . timeIntervalSince1970 - time. timeIntervalSince1970) . formatted ( . number. precision ( . fractionLength( 2 ) ) ) ) " )
176197
177198 // add live video assets to sessions
199+ time = Date ( )
200+ print ( " Starting add live video assets to sessions: \( ( Date ( ) . timeIntervalSince1970 - time. timeIntervalSince1970) . formatted ( . number. precision ( . fractionLength( 2 ) ) ) ) " )
178201 backgroundRealm. objects ( SessionAsset . self) . filter ( " rawAssetType == %@ " , SessionAssetType . liveStreamVideo. rawValue) . forEach { liveAsset in
179202 if let session = backgroundRealm. objects ( Session . self) . filter ( " ANY event.year == %d AND number == %@ " , liveAsset. year, liveAsset. sessionId) . first {
180203 if !session. assets. contains ( liveAsset) {
181204 session. assets. append ( liveAsset)
182205 }
183206 }
184207 }
208+ print ( " Ending add live video assets: \( ( Date ( ) . timeIntervalSince1970 - time. timeIntervalSince1970) . formatted ( . number. precision ( . fractionLength( 2 ) ) ) ) " )
185209
186210 // Associate session resources with Session objects in database
211+ time = Date ( )
212+ print ( " Starting Associate session resources with Session objects in database: \( ( Date ( ) . timeIntervalSince1970 - time. timeIntervalSince1970) . formatted ( . number. precision ( . fractionLength( 2 ) ) ) ) " )
187213 backgroundRealm. objects ( RelatedResource . self) . filter ( " type == %@ " , RelatedResourceType . session. rawValue) . forEach { resource in
188214 if let session = backgroundRealm. object ( ofType: Session . self, forPrimaryKey: resource. identifier) {
189215 resource. session = session
190216 }
191217 }
218+ print ( " Ending Associate session resources: \( ( Date ( ) . timeIntervalSince1970 - time. timeIntervalSince1970) . formatted ( . number. precision ( . fractionLength( 2 ) ) ) ) " )
192219
193220 // Remove tracks that don't include any future session instances nor any sessions with video/live video
221+ time = Date ( )
222+ print ( " Starting Remove tracks that don't include any future session instances nor any sessions with video/live video: \( ( Date ( ) . timeIntervalSince1970 - time. timeIntervalSince1970) . formatted ( . number. precision ( . fractionLength( 2 ) ) ) ) " )
194223 let emptyTracks = backgroundRealm. objects ( Track . self)
195224 . filter ( " SUBQUERY(sessions, $session, ANY $session.assets.rawAssetType = %@ OR ANY $session.assets.rawAssetType = %@).@count == 0 " , SessionAssetType . streamingVideo. rawValue, SessionAssetType . liveStreamVideo. rawValue)
196225 backgroundRealm. delete ( emptyTracks)
226+ print ( " Ending Remove tracks: \( ( Date ( ) . timeIntervalSince1970 - time. timeIntervalSince1970) . formatted ( . number. precision ( . fractionLength( 2 ) ) ) ) " )
197227
198228 // Create schedule view
229+ time = Date ( )
230+ print ( " Starting Create schedule view: \( ( Date ( ) . timeIntervalSince1970 - time. timeIntervalSince1970) . formatted ( . number. precision ( . fractionLength( 2 ) ) ) ) " )
199231 backgroundRealm. delete ( backgroundRealm. objects ( ScheduleSection . self) )
200-
201- let instances = backgroundRealm. objects ( SessionInstance . self) . sorted ( by: SessionInstance . standardSort)
202-
203- var previousStartTime : Date ?
204- for instance in instances {
205- guard instance. startTime != previousStartTime else { continue }
206-
207- autoreleasepool {
208- let instancesForSection = instances. filter ( { $0. startTime == instance. startTime } )
209-
210- let section = ScheduleSection ( )
211-
212- section. representedDate = instance. startTime
213- section. eventIdentifier = instance. eventIdentifier
214- section. instances. removeAll ( )
215- section. instances. append ( objectsIn: instancesForSection)
216- section. identifier = ScheduleSection . identifierFormatter. string ( from: instance. startTime)
217-
218- backgroundRealm. add ( section, update: . all)
219-
220- previousStartTime = instance. startTime
221- }
232+ let instances = backgroundRealm. objects ( SessionInstance . self)
233+
234+ // Group all instances by common start time
235+ // Technically, a secondary grouping on event should be used, in practice we haven't seen
236+ // separate events that overlap in time. Someday this might hurt
237+ Dictionary ( grouping: instances, by: \. startTime) . forEach { startTime, instances in
238+ let section = ScheduleSection ( )
239+ section. representedDate = startTime
240+ section. eventIdentifier = instances [ 0 ] . eventIdentifier // 0 index ok, Dictionary grouping will never give us an empty array
241+ section. instances. removeAll ( )
242+ section. instances. append ( objectsIn: instances)
243+ section. identifier = ScheduleSection . identifierFormatter. string ( from: startTime)
244+
245+ backgroundRealm. add ( section, update: . all)
222246 }
223- } , disableAutorefresh: true , completionBlock: completion)
247+ print ( " Ending Create schedule view: \( ( Date ( ) . timeIntervalSince1970 - time. timeIntervalSince1970) . formatted ( . number. precision ( . fractionLength( 2 ) ) ) ) " )
248+ }
224249 }
225250
226251 internal func store( liveVideosResult: Result < [ SessionAsset ] , APIError > ) {
@@ -271,7 +296,7 @@ public final class Storage {
271296 return
272297 }
273298
274- performSerializedBackgroundWrite ( writeBlock : { backgroundRealm in
299+ performSerializedBackgroundWrite ( disableAutorefresh : true , completionBlock : completion ) { backgroundRealm in
275300 let existingSections = backgroundRealm. objects ( FeaturedSection . self)
276301 for section in existingSections {
277302 section. content. forEach { backgroundRealm. delete ( $0) }
@@ -287,7 +312,7 @@ public final class Storage {
287312 content. session = backgroundRealm. object ( ofType: Session . self, forPrimaryKey: content. sessionId)
288313 }
289314 }
290- } , disableAutorefresh : true , completionBlock : completion )
315+ }
291316 }
292317
293318 internal func store( configResult: Result < ConfigResponse , APIError > , completion: @escaping ( Error ? ) -> Void ) {
@@ -304,20 +329,20 @@ public final class Storage {
304329 return
305330 }
306331
307- performSerializedBackgroundWrite ( writeBlock : { backgroundRealm in
332+ performSerializedBackgroundWrite ( disableAutorefresh : false , completionBlock : completion ) { backgroundRealm in
308333 // We currently only care about whatever the latest event hero is.
309334 let existingHeroData = backgroundRealm. objects ( EventHero . self)
310335 backgroundRealm. delete ( existingHeroData)
311- } , disableAutorefresh : false , completionBlock : completion )
336+ }
312337
313338 guard let hero = response. eventHero else {
314339 os_log ( " Config response didn't contain an event hero " , log: self . log, type: . debug)
315340 return
316341 }
317342
318- performSerializedBackgroundWrite ( writeBlock : { backgroundRealm in
343+ performSerializedBackgroundWrite ( disableAutorefresh : false , completionBlock : completion ) { backgroundRealm in
319344 backgroundRealm. add ( hero, update: . all)
320- } , disableAutorefresh : false , completionBlock : completion )
345+ }
321346 }
322347
323348 private let serialQueue = DispatchQueue ( label: " Database Serial " , qos: . userInteractive)
@@ -330,11 +355,11 @@ public final class Storage {
330355 /// - createTransaction: Whether the method should create its own write transaction or use the one already in place
331356 /// - notificationTokensToSkip: An array of `NotificationToken` that should not be notified when the write is committed
332357 /// - completionBlock: A block to be called when the operation is completed (called on the main queue)
333- internal func performSerializedBackgroundWrite( writeBlock: @escaping ( Realm ) throws -> Void ,
334- disableAutorefresh: Bool = false ,
358+ internal func performSerializedBackgroundWrite( disableAutorefresh: Bool = false ,
335359 createTransaction: Bool = true ,
336360 notificationTokensToSkip: [ NotificationToken ] = [ ] ,
337- completionBlock: ( ( Error ? ) -> Void ) ? = nil ) {
361+ completionBlock: ( ( Error ? ) -> Void ) ? = nil ,
362+ writeBlock: @escaping ( Realm ) throws -> Void ) {
338363 if disableAutorefresh { realm. autorefresh = false }
339364
340365 serialQueue. async {
@@ -394,13 +419,13 @@ public final class Storage {
394419 public func modify< T> ( _ object: T , with writeBlock: @escaping ( T ) -> Void ) where T: ThreadConfined {
395420 let safeObject = ThreadSafeReference ( to: object)
396421
397- performSerializedBackgroundWrite ( writeBlock: { backgroundRealm in
422+ performSerializedBackgroundWrite ( createTransaction : false , writeBlock: { backgroundRealm in
398423 guard let resolvedObject = backgroundRealm. resolve ( safeObject) else { return }
399424
400425 try backgroundRealm. write {
401426 writeBlock ( resolvedObject)
402427 }
403- } , createTransaction : false )
428+ } )
404429 }
405430
406431 /// Gives you an opportunity to update `objects` on a background queue
@@ -416,7 +441,7 @@ public final class Storage {
416441 public func modify< T> ( _ objects: [ T ] , with writeBlock: @escaping ( [ T ] ) -> Void ) where T: ThreadConfined {
417442 let safeObjects = objects. map { ThreadSafeReference ( to: $0) }
418443
419- performSerializedBackgroundWrite ( writeBlock: { [ weak self] backgroundRealm in
444+ performSerializedBackgroundWrite ( createTransaction : false , writeBlock: { [ weak self] backgroundRealm in
420445 guard let self = self else { return }
421446
422447 let resolvedObjects = safeObjects. compactMap { backgroundRealm. resolve ( $0) }
@@ -429,7 +454,7 @@ public final class Storage {
429454 try backgroundRealm. write {
430455 writeBlock ( resolvedObjects)
431456 }
432- } , createTransaction : false )
457+ } )
433458 }
434459
435460 public lazy var events : Observable < Results < Event > > = {
@@ -455,9 +480,9 @@ public final class Storage {
455480 }
456481
457482 public func setFavorite( _ isFavorite: Bool , onSessionsWithIDs ids: [ String ] ) {
458- performSerializedBackgroundWrite ( writeBlock: { realm in
483+ performSerializedBackgroundWrite ( disableAutorefresh : false , createTransaction : true , writeBlock: { realm in
459484 let sessions = realm. objects ( Session . self) . filter ( NSPredicate ( format: " identifier IN %@ " , ids) )
460-
485+
461486 sessions. forEach { session in
462487 if isFavorite {
463488 guard !session. isFavorite else { return }
@@ -466,7 +491,7 @@ public final class Storage {
466491 session. favorites. forEach { $0. isDeleted = true }
467492 }
468493 }
469- } , disableAutorefresh : false , createTransaction : true )
494+ } )
470495 }
471496
472497 public lazy var eventsObservable : Observable < Results < Event > > = {
0 commit comments