2021-04-13 13:46:42 +02:00
//
// T h r e a d V i e w M o d e l + D i f f a b l e . s w i f t
// M a s t o d o n
//
// C r e a t e d b y M a i n a s u K C i r n o o n 2 0 2 1 - 4 - 1 2 .
//
import UIKit
import Combine
import CoreData
2021-06-21 10:38:59 +02:00
import CoreDataStack
import MastodonSDK
2021-04-13 13:46:42 +02:00
extension ThreadViewModel {
2022-01-27 14:23:39 +01:00
@ MainActor
2021-04-13 13:46:42 +02:00
func setupDiffableDataSource (
2022-01-27 14:23:39 +01:00
tableView : UITableView ,
statusTableViewCellDelegate : StatusTableViewCellDelegate
2021-04-13 13:46:42 +02:00
) {
2022-01-27 14:23:39 +01:00
diffableDataSource = StatusSection . diffableDataSource (
tableView : tableView ,
context : context ,
configuration : StatusSection . Configuration (
statusTableViewCellDelegate : statusTableViewCellDelegate ,
timelineMiddleLoaderTableViewCellDelegate : nil
)
2021-04-13 13:46:42 +02:00
)
2022-01-27 14:23:39 +01:00
// m a k e i n i t i a l s n a p s h o t a n i m a t i o n s m o o t h
var snapshot = NSDiffableDataSourceSnapshot < StatusSection , StatusItem > ( )
2021-04-13 13:46:42 +02:00
snapshot . appendSections ( [ . main ] )
2022-01-27 14:23:39 +01:00
if let root = self . root {
if case let . root ( threadContext ) = root ,
let status = threadContext . status . object ( in : context . managedObjectContext ) ,
status . inReplyToID != nil
{
snapshot . appendItems ( [ . topLoader ] , toSection : . main )
}
snapshot . appendItems ( [ . thread ( root ) ] , toSection : . main )
} else {
2021-04-13 13:46:42 +02:00
}
2022-01-29 10:02:30 +01:00
diffableDataSource ? . apply ( snapshot , animatingDifferences : false )
2021-04-13 13:46:42 +02:00
2022-01-27 14:23:39 +01:00
$ threadContext
. receive ( on : DispatchQueue . main )
. sink { [ weak self ] threadContext in
guard let self = self else { return }
guard let _ = threadContext else {
return
}
self . loadThreadStateMachine . enter ( LoadThreadState . Loading . self )
}
. store ( in : & disposeBag )
2021-04-13 13:46:42 +02:00
Publishers . CombineLatest3 (
2022-01-27 14:23:39 +01:00
$ root ,
mastodonStatusThreadViewModel . $ ancestors ,
mastodonStatusThreadViewModel . $ descendants
2021-04-13 13:46:42 +02:00
)
2022-01-27 14:23:39 +01:00
. throttle ( for : 1 , scheduler : DispatchQueue . main , latest : true )
. sink { [ weak self ] root , ancestors , descendants in
2021-04-13 13:46:42 +02:00
guard let self = self else { return }
guard let diffableDataSource = self . diffableDataSource else { return }
2022-01-27 14:23:39 +01:00
Task { @ MainActor in
let oldSnapshot = diffableDataSource . snapshot ( )
var newSnapshot = NSDiffableDataSourceSnapshot < StatusSection , StatusItem > ( )
newSnapshot . appendSections ( [ . main ] )
2021-04-13 13:46:42 +02:00
2022-01-27 14:23:39 +01:00
// t o p l o a d e r
let _hasReplyTo : Bool ? = try ? await self . context . managedObjectContext . perform {
guard case let . root ( threadContext ) = root else { return nil }
guard let status = threadContext . status . object ( in : self . context . managedObjectContext ) else { return nil }
return status . inReplyToID != nil
2021-04-13 13:46:42 +02:00
}
2022-01-27 14:23:39 +01:00
if let hasReplyTo = _hasReplyTo , hasReplyTo {
let state = self . loadThreadStateMachine . currentState
if state is LoadThreadState . NoMore {
// d o n o t h i n g
} else {
newSnapshot . appendItems ( [ . topLoader ] , toSection : . main )
}
2021-04-13 13:46:42 +02:00
}
2022-01-27 14:23:39 +01:00
// r e p l i e s
newSnapshot . appendItems ( ancestors . reversed ( ) , toSection : . main )
// r o o t
if let root = root {
let item = StatusItem . thread ( root )
newSnapshot . appendItems ( [ item ] , toSection : . main )
}
// l e a f s
newSnapshot . appendItems ( descendants , toSection : . main )
// b o t t o m l o a d e r
if let currentState = self . loadThreadStateMachine . currentState {
switch currentState {
case is LoadThreadState . Initial ,
is LoadThreadState . Loading ,
is LoadThreadState . Fail :
newSnapshot . appendItems ( [ . bottomLoader ] , toSection : . main )
default :
break
}
}
let hasChanges = newSnapshot . itemIdentifiers != oldSnapshot . itemIdentifiers
if ! hasChanges {
self . logger . log ( level : . debug , " \( ( #file as NSString ) . lastPathComponent , privacy : . public ) [ \( #line , privacy : . public ) ], \( #function , privacy : . public ) : snapshot not changes " )
2021-04-13 13:46:42 +02:00
return
2022-01-27 14:23:39 +01:00
} else {
self . logger . log ( level : . debug , " \( ( #file as NSString ) . lastPathComponent , privacy : . public ) [ \( #line , privacy : . public ) ], \( #function , privacy : . public ) : snapshot has changes " )
2021-04-13 13:46:42 +02:00
}
2022-01-27 14:23:39 +01:00
guard let difference = self . calculateReloadSnapshotDifference (
tableView : tableView ,
oldSnapshot : oldSnapshot ,
newSnapshot : newSnapshot
) else {
await self . updateDataSource ( snapshot : newSnapshot , animatingDifferences : false )
self . logger . log ( level : . debug , " \( ( #file as NSString ) . lastPathComponent , privacy : . public ) [ \( #line , privacy : . public ) ], \( #function , privacy : . public ) : applied new snapshot without tweak " )
return
2021-04-13 13:46:42 +02:00
}
2022-01-27 14:23:39 +01:00
self . logger . log ( level : . debug , " \( ( #file as NSString ) . lastPathComponent , privacy : . public ) [ \( #line , privacy : . public ) ], \( #function , privacy : . public ) : [Snapshot] oldSnapshot: \( oldSnapshot . itemIdentifiers . debugDescription ) " )
self . logger . log ( level : . debug , " \( ( #file as NSString ) . lastPathComponent , privacy : . public ) [ \( #line , privacy : . public ) ], \( #function , privacy : . public ) : [Snapshot] newSnapshot: \( newSnapshot . itemIdentifiers . debugDescription ) " )
await self . updateSnapshotUsingReloadData (
tableView : tableView ,
oldSnapshot : oldSnapshot ,
newSnapshot : newSnapshot ,
difference : difference
)
} // e n d T a s k
2021-04-13 13:46:42 +02:00
}
. store ( in : & disposeBag )
2022-01-27 14:23:39 +01:00
// P u b l i s h e r s . C o m b i n e L a t e s t 3 (
// r o o t I t e m . r e m o v e D u p l i c a t e s ( ) ,
// a n c e s t o r I t e m s . r e m o v e D u p l i c a t e s ( ) ,
// d e s c e n d a n t I t e m s . r e m o v e D u p l i c a t e s ( )
// )
// . r e c e i v e ( o n : R u n L o o p . m a i n )
// . s i n k { [ w e a k s e l f ] r o o t I t e m , a n c e s t o r I t e m s , d e s c e n d a n t I t e m s i n
// g u a r d l e t s e l f = s e l f e l s e { r e t u r n }
// v a r i t e m s : [ I t e m ] = [ ]
// r o o t I t e m . f l a t M a p { i t e m s . a p p e n d ( $ 0 ) }
// i t e m s . a p p e n d ( c o n t e n t s O f : a n c e s t o r I t e m s )
// i t e m s . a p p e n d ( c o n t e n t s O f : d e s c e n d a n t I t e m s )
// s e l f . u p d a t e D e l e t e d S t a t u s ( f o r : i t e m s )
// }
// . s t o r e ( i n : & d i s p o s e B a g )
//
// P u b l i s h e r s . C o m b i n e L a t e s t 4 (
// r o o t I t e m ,
// a n c e s t o r I t e m s ,
// d e s c e n d a n t I t e m s ,
// e x i s t S t a t u s F e t c h e d R e s u l t s C o n t r o l l e r . o b j e c t I D s
// )
// . d e b o u n c e ( f o r : . m i l l i s e c o n d s ( 1 0 0 ) , s c h e d u l e r : R u n L o o p . m a i n ) / / s o m e m a g i c t o a v o i d j i t t e r
// . s i n k { [ w e a k s e l f ] r o o t I t e m , a n c e s t o r I t e m s , d e s c e n d a n t I t e m s , e x i s t O b j e c t I D s i n
// g u a r d l e t s e l f = s e l f e l s e { r e t u r n }
// g u a r d l e t t a b l e V i e w = s e l f . t a b l e V i e w ,
// l e t n a v i g a t i o n B a r = s e l f . c o n t e n t O f f s e t A d j u s t a b l e T i m e l i n e V i e w C o n t r o l l e r D e l e g a t e ? . n a v i g a t i o n B a r ( )
// e l s e { r e t u r n }
//
// g u a r d l e t d i f f a b l e D a t a S o u r c e = s e l f . d i f f a b l e D a t a S o u r c e e l s e { r e t u r n }
// l e t o l d S n a p s h o t = d i f f a b l e D a t a S o u r c e . s n a p s h o t ( )
//
// v a r n e w S n a p s h o t = N S D i f f a b l e D a t a S o u r c e S n a p s h o t < S t a t u s S e c t i o n , I t e m > ( )
// n e w S n a p s h o t . a p p e n d S e c t i o n s ( [ . m a i n ] )
//
// l e t c u r r e n t S t a t e = s e l f . l o a d T h r e a d S t a t e M a c h i n e . c u r r e n t S t a t e
//
// / / r e p l y t o
// i f s e l f . r o o t N o d e . v a l u e ? . r e p l y T o I D ! = n i l , ! ( c u r r e n t S t a t e i s L o a d T h r e a d S t a t e . N o M o r e ) {
// n e w S n a p s h o t . a p p e n d I t e m s ( [ . t o p L o a d e r ] , t o S e c t i o n : . m a i n )
// }
//
// l e t a n c e s t o r I t e m s = a n c e s t o r I t e m s . f i l t e r { i t e m i n
// g u a r d c a s e l e t . r e p l y ( s t a t u s O b j e c t I D , _ ) = i t e m e l s e { r e t u r n f a l s e }
// r e t u r n e x i s t O b j e c t I D s . c o n t a i n s ( s t a t u s O b j e c t I D )
// }
// n e w S n a p s h o t . a p p e n d I t e m s ( a n c e s t o r I t e m s , t o S e c t i o n : . m a i n )
//
// / / r o o t
// i f l e t r o o t I t e m = r o o t I t e m ,
// c a s e l e t . r o o t ( o b j e c t I D , _ ) = r o o t I t e m ,
// e x i s t O b j e c t I D s . c o n t a i n s ( o b j e c t I D ) {
// n e w S n a p s h o t . a p p e n d I t e m s ( [ r o o t I t e m ] , t o S e c t i o n : . m a i n )
// }
//
// / / l e a f
// i f ! ( c u r r e n t S t a t e i s L o a d T h r e a d S t a t e . N o M o r e ) {
// n e w S n a p s h o t . a p p e n d I t e m s ( [ . b o t t o m L o a d e r ] , t o S e c t i o n : . m a i n )
// }
//
// l e t d e s c e n d a n t I t e m s = d e s c e n d a n t I t e m s . f i l t e r { i t e m i n
// s w i t c h i t e m {
// c a s e . l e a f ( l e t s t a t u s O b j e c t I D , _ ) :
// r e t u r n e x i s t O b j e c t I D s . c o n t a i n s ( s t a t u s O b j e c t I D )
// d e f a u l t :
// r e t u r n t r u e
// }
// }
// n e w S n a p s h o t . a p p e n d I t e m s ( d e s c e n d a n t I t e m s , t o S e c t i o n : . m a i n )
//
// / / d i f f e r e n c e f o r f i r s t v i s i b l e i t e m e x c l u d e . t o p L o a d e r
// g u a r d l e t d i f f e r e n c e = s e l f . c a l c u l a t e R e l o a d S n a p s h o t D i f f e r e n c e ( n a v i g a t i o n B a r : n a v i g a t i o n B a r , t a b l e V i e w : t a b l e V i e w , o l d S n a p s h o t : o l d S n a p s h o t , n e w S n a p s h o t : n e w S n a p s h o t ) e l s e {
// d i f f a b l e D a t a S o u r c e . a p p l y ( n e w S n a p s h o t )
// r e t u r n
// }
//
// / / a d d i t i o n a l m a r g i n f o r . t o p L o a d e r
// l e t o l d T o p M a r g i n : C G F l o a t = {
// l e t m a r g i n H e i g h t = T i m e l i n e T o p L o a d e r T a b l e V i e w C e l l . c e l l H e i g h t
// i f o l d S n a p s h o t . i t e m I d e n t i f i e r s . c o n t a i n s ( . t o p L o a d e r ) {
// r e t u r n m a r g i n H e i g h t
// }
// i f ! a n c e s t o r I t e m s . i s E m p t y {
// r e t u r n m a r g i n H e i g h t
// }
//
// r e t u r n . z e r o
// } ( )
//
// l e t o l d R o o t C e l l : U I T a b l e V i e w C e l l ? = {
// g u a r d l e t r o o t I t e m = r o o t I t e m e l s e { r e t u r n n i l }
// g u a r d l e t i n d e x = o l d S n a p s h o t . i n d e x O f I t e m ( r o o t I t e m ) e l s e { r e t u r n n i l }
// g u a r d l e t c e l l = t a b l e V i e w . c e l l F o r R o w ( a t : I n d e x P a t h ( r o w : i n d e x , s e c t i o n : 0 ) ) e l s e { r e t u r n n i l }
// r e t u r n c e l l
// } ( )
// / / s a v e h e i g h t b e f o r e c e l l r e u s e
// l e t o l d R o o t C e l l H e i g h t = o l d R o o t C e l l ? . f r a m e . h e i g h t
//
// d i f f a b l e D a t a S o u r c e . r e l o a d D a t a ( s n a p s h o t : n e w S n a p s h o t ) {
// g u a r d l e t _ = r o o t I t e m e l s e {
// r e t u r n
// }
// i f l e t o l d R o o t C e l l H e i g h t = o l d R o o t C e l l H e i g h t {
// / / s e t b o t t o m i n s e t . M a k e r o o t i t e m p i n t o t o p ( w i t h m a r g i n ) .
// l e t b o t t o m S p a c i n g = t a b l e V i e w . s a f e A r e a L a y o u t G u i d e . l a y o u t F r a m e . h e i g h t - o l d R o o t C e l l H e i g h t - o l d T o p M a r g i n
// t a b l e V i e w . c o n t e n t I n s e t . b o t t o m = m a x ( 0 , b o t t o m S p a c i n g )
// }
//
// / / s e t s c r o l l p o s i t i o n
// t a b l e V i e w . s c r o l l T o R o w ( a t : d i f f e r e n c e . t a r g e t I n d e x P a t h , a t : . t o p , a n i m a t e d : f a l s e )
// l e t c o n t e n t O f f s e t Y : C G F l o a t = {
// v a r o f f s e t : C G F l o a t = t a b l e V i e w . c o n t e n t O f f s e t . y - d i f f e r e n c e . o f f s e t
// i f t a b l e V i e w . c o n t e n t I n s e t . b o t t o m ! = 0 . 0 & & d e s c e n d a n t I t e m s . i s E m p t y {
// / / n e e d s r e s t o r e t o p m a r g i n i f b o t t o m i n s e t a d j u s t e d A N D n o d e s c e n d a n t I t e m s
// o f f s e t + = o l d T o p M a r g i n
// }
// r e t u r n o f f s e t
// } ( )
// t a b l e V i e w . s e t C o n t e n t O f f s e t ( C G P o i n t ( x : 0 , y : c o n t e n t O f f s e t Y ) , a n i m a t e d : f a l s e )
// }
// }
// . s t o r e ( i n : & d i s p o s e B a g )
}
}
extension ThreadViewModel {
@ MainActor func updateDataSource (
snapshot : NSDiffableDataSourceSnapshot < StatusSection , StatusItem > ,
animatingDifferences : Bool
) async {
diffableDataSource ? . apply ( snapshot , animatingDifferences : animatingDifferences )
}
@ MainActor func updateSnapshotUsingReloadData (
snapshot : NSDiffableDataSourceSnapshot < StatusSection , StatusItem >
) async {
if #available ( iOS 15.0 , * ) {
await self . diffableDataSource ? . applySnapshotUsingReloadData ( snapshot )
} else {
diffableDataSource ? . applySnapshot ( snapshot , animated : false , completion : nil )
}
2021-04-13 13:46:42 +02:00
}
2022-01-27 14:23:39 +01:00
// S o m e U I t w e a k s t o p r e s e n t r e p l i e s a n d c o n v e r s a t i o n s m o o t h l y
@ MainActor private func updateSnapshotUsingReloadData (
tableView : UITableView ,
oldSnapshot : NSDiffableDataSourceSnapshot < StatusSection , StatusItem > ,
newSnapshot : NSDiffableDataSourceSnapshot < StatusSection , StatusItem > ,
difference : ThreadViewModel . Difference // < S t a t u s I t e m >
) async {
let replies : [ StatusItem ] = {
newSnapshot . itemIdentifiers . filter { item in
guard case let . thread ( thread ) = item else { return false }
guard case . reply = thread else { return false }
return true
}
} ( )
// a d d i t i o n a l m a r g i n f o r . t o p L o a d e r
let oldTopMargin : CGFloat = {
let marginHeight = TimelineTopLoaderTableViewCell . cellHeight
if oldSnapshot . itemIdentifiers . contains ( . topLoader ) || ! replies . isEmpty {
return marginHeight
}
return . zero
} ( )
await self . updateSnapshotUsingReloadData ( snapshot : newSnapshot )
// n o t e :
// t w e a k t h e c o n t e n t o f f s e t a n d b o t t o m i n s e t
// m a k e t h e t a b l e v i e w s t a b l e w h e n d a t a r e l o a d
// t h e k e y p o i n t i s s e t t h e b o t t o m i n s e t t o m a k e t h e r o o t p a d d i n g w i t h " T o p L o a d e r H e i g h t " t o t o p e d g e
// a n d r e s t o r e t h e " T o p L o a d e r H e i g h t " w h e n b o t t o m i n s e t a d j u s t e d
// s e t b o t t o m i n s e t . M a k e r o o t i t e m p i n t o t o p .
if let item = root . flatMap ( { StatusItem . thread ( $0 ) } ) ,
let index = newSnapshot . indexOfItem ( item ) ,
let cell = tableView . cellForRow ( at : IndexPath ( row : index , section : 0 ) )
{
// a l w a y s s e t b o t t o m i n s e t d u e t o l a z y r e p l y l o a d i n g
// o t h e r w i s e t a b l e V i e w w i l l j u m p w h e n i n s e r t r e p l i e s
let bottomSpacing = tableView . safeAreaLayoutGuide . layoutFrame . height - cell . frame . height - oldTopMargin
let additionalInset = round ( tableView . contentSize . height - cell . frame . maxY )
tableView . contentInset . bottom = max ( 0 , bottomSpacing - additionalInset )
self . logger . log ( level : . debug , " \( ( #file as NSString ) . lastPathComponent , privacy : . public ) [ \( #line , privacy : . public ) ], \( #function , privacy : . public ) : content inset bottom: \( tableView . contentInset . bottom ) " )
}
// s e t s c r o l l p o s i t i o n
tableView . scrollToRow ( at : difference . targetIndexPath , at : . top , animated : false )
tableView . contentOffset . y = {
var offset : CGFloat = tableView . contentOffset . y - difference . sourceDistanceToTableViewTopEdge
if tableView . contentInset . bottom != 0.0 {
// n e e d s r e s t o r e t o p m a r g i n i f b o t t o m i n s e t a d j u s t e d
offset += oldTopMargin
}
return offset
} ( )
self . logger . log ( level : . debug , " \( ( #file as NSString ) . lastPathComponent , privacy : . public ) [ \( #line , privacy : . public ) ], \( #function , privacy : . public ) : applied new snapshot " )
}
2021-04-13 13:46:42 +02:00
}
extension ThreadViewModel {
2022-01-27 14:23:39 +01:00
struct Difference {
let item : StatusItem
2021-04-13 13:46:42 +02:00
let sourceIndexPath : IndexPath
2022-01-27 14:23:39 +01:00
let sourceDistanceToTableViewTopEdge : CGFloat
2021-04-13 13:46:42 +02:00
let targetIndexPath : IndexPath
}
2022-01-27 14:23:39 +01:00
@ MainActor private func calculateReloadSnapshotDifference (
2021-04-13 13:46:42 +02:00
tableView : UITableView ,
2022-01-27 14:23:39 +01:00
oldSnapshot : NSDiffableDataSourceSnapshot < StatusSection , StatusItem > ,
newSnapshot : NSDiffableDataSourceSnapshot < StatusSection , StatusItem >
) -> Difference ? {
2021-04-13 13:46:42 +02:00
guard oldSnapshot . numberOfItems != 0 else { return nil }
2022-01-27 14:23:39 +01:00
guard let indexPathsForVisibleRows = tableView . indexPathsForVisibleRows ? . sorted ( ) else { return nil }
// f i n d i n d e x o f t h e f i r s t v i s i b l e i t e m i n b o t h o l d a n d n e w s n a p s h o t
2021-04-13 13:46:42 +02:00
var _index : Int ?
let items = oldSnapshot . itemIdentifiers ( inSection : . main )
for ( i , item ) in items . enumerated ( ) {
2022-01-27 14:23:39 +01:00
guard let indexPath = indexPathsForVisibleRows . first ( where : { $0 . row = = i } ) else { continue }
guard newSnapshot . indexOfItem ( item ) != nil else { continue }
let rectForCell = tableView . rectForRow ( at : indexPath )
let distanceToTableViewTopEdge = tableView . convert ( rectForCell , to : nil ) . origin . y - tableView . safeAreaInsets . top
guard distanceToTableViewTopEdge >= 0 else { continue }
2021-04-13 13:46:42 +02:00
_index = i
break
}
2022-01-27 14:23:39 +01:00
guard let index = _index else { return nil }
2021-04-13 13:46:42 +02:00
let sourceIndexPath = IndexPath ( row : index , section : 0 )
2022-01-27 14:23:39 +01:00
let rectForSourceItemCell = tableView . rectForRow ( at : sourceIndexPath )
let sourceDistanceToTableViewTopEdge = tableView . convert ( rectForSourceItemCell , to : nil ) . origin . y - tableView . safeAreaInsets . top
guard sourceIndexPath . section < oldSnapshot . numberOfSections ,
sourceIndexPath . row < oldSnapshot . numberOfItems ( inSection : oldSnapshot . sectionIdentifiers [ sourceIndexPath . section ] )
else { return nil }
let sectionIdentifier = oldSnapshot . sectionIdentifiers [ sourceIndexPath . section ]
let item = oldSnapshot . itemIdentifiers ( inSection : sectionIdentifier ) [ sourceIndexPath . row ]
guard let targetIndexPathRow = newSnapshot . indexOfItem ( item ) ,
let newSectionIdentifier = newSnapshot . sectionIdentifier ( containingItem : item ) ,
let targetIndexPathSection = newSnapshot . indexOfSection ( newSectionIdentifier )
else { return nil }
let targetIndexPath = IndexPath ( row : targetIndexPathRow , section : targetIndexPathSection )
2021-04-13 13:46:42 +02:00
return Difference (
item : item ,
sourceIndexPath : sourceIndexPath ,
2022-01-27 14:23:39 +01:00
sourceDistanceToTableViewTopEdge : sourceDistanceToTableViewTopEdge ,
targetIndexPath : targetIndexPath
2021-04-13 13:46:42 +02:00
)
}
}
2021-06-21 10:38:59 +02:00
2022-01-27 14:23:39 +01:00
// e x t e n s i o n T h r e a d V i e w M o d e l {
// p r i v a t e f u n c u p d a t e D e l e t e d S t a t u s ( f o r i t e m s : [ I t e m ] ) {
// l e t p a r e n t M a n a g e d O b j e c t C o n t e x t = c o n t e x t . m a n a g e d O b j e c t C o n t e x t
// l e t m a n a g e d O b j e c t C o n t e x t = N S M a n a g e d O b j e c t C o n t e x t ( c o n c u r r e n c y T y p e : . p r i v a t e Q u e u e C o n c u r r e n c y T y p e )
// m a n a g e d O b j e c t C o n t e x t . p a r e n t = p a r e n t M a n a g e d O b j e c t C o n t e x t
// m a n a g e d O b j e c t C o n t e x t . p e r f o r m {
// v a r s t a t u s I D s : [ S t a t u s . I D ] = [ ]
// f o r i t e m i n i t e m s {
// s w i t c h i t e m {
// c a s e . r o o t ( l e t o b j e c t I D , _ ) :
// g u a r d l e t s t a t u s = m a n a g e d O b j e c t C o n t e x t . o b j e c t ( w i t h : o b j e c t I D ) a s ? S t a t u s e l s e { c o n t i n u e }
// s t a t u s I D s . a p p e n d ( s t a t u s . i d )
// c a s e . r e p l y ( l e t o b j e c t I D , _ ) :
// g u a r d l e t s t a t u s = m a n a g e d O b j e c t C o n t e x t . o b j e c t ( w i t h : o b j e c t I D ) a s ? S t a t u s e l s e { c o n t i n u e }
// s t a t u s I D s . a p p e n d ( s t a t u s . i d )
// c a s e . l e a f ( l e t o b j e c t I D , _ ) :
// g u a r d l e t s t a t u s = m a n a g e d O b j e c t C o n t e x t . o b j e c t ( w i t h : o b j e c t I D ) a s ? S t a t u s e l s e { c o n t i n u e }
// s t a t u s I D s . a p p e n d ( s t a t u s . i d )
// d e f a u l t :
// c o n t i n u e
// }
// }
// D i s p a t c h Q u e u e . m a i n . a s y n c { [ w e a k s e l f ] i n
// g u a r d l e t s e l f = s e l f e l s e { r e t u r n }
// s e l f . e x i s t S t a t u s F e t c h e d R e s u l t s C o n t r o l l e r . s t a t u s I D s . v a l u e = s t a t u s I D s
// }
// }
// }
// }