Skip to content

Commit

Permalink
Add update finished announcer API
Browse files Browse the repository at this point in the history
Summary:
Adding a new API to support adding 1:many listeners to observe when an `IGListAdapter` finishes performing an update event. It supports:

- `performUpdates:`
- `reloadData...`
- Even handling update triggered from //inside// a section controller

Differential Revision: D6108096

fbshipit-source-id: d0b43d83f1963fdbf6ef388685cbd8f6890177fa
  • Loading branch information
Ryan Nystrom authored and facebook-github-bot committed Oct 20, 2017
1 parent 48aff22 commit 5cf01cc
Show file tree
Hide file tree
Showing 10 changed files with 375 additions and 8 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -9,6 +9,8 @@ The changelog for `IGListKit`. Also see the [releases](https://github.com/instag

- Added `-[IGListSectionController didHighlightItemAtIndex:]` and `-[IGListSectionController didUnhighlightItemAtIndex:]` APIs to support `UICollectionView` cell highlighting. [Kevin Delannoy](https://github.com/delannoyk) [(#933)](https://github.com/Instagram/IGListKit/pull/933)

- Added a new listener API to be notified when `IGListAdapter` finishes updating. Add listeners via `-[IGListAdapter addUpdateListener:]` with objects conforming to the new `IGListAdapterUpdateListener` protocol. [Ryan Nystrom](https://github.com/rnystrom) [(tbd)](https://github.com/Instagram/IGListKit/pull/tbd)

### Fixes

- Weakly reference the `UICollectionView` in coalescence so that it can be released if the rest of system is destroyed. [Ryan Nystrom](https://github.com/rnystrom) [(#tbd)](https://github.com/Instagram/IGListKit/pull/tbd)
Expand Down
14 changes: 14 additions & 0 deletions IGListKit.xcodeproj/project.pbxproj
Expand Up @@ -196,6 +196,10 @@
294652BB1EA927750063BDD9 /* IGListSectionMap+DebugDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = 290DF36E1E931457009FE456 /* IGListSectionMap+DebugDescription.h */; settings = {ATTRIBUTES = (Private, ); }; };
294652BC1EA927750063BDD9 /* UICollectionView+DebugDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = 290DF3561E930CE2009FE456 /* UICollectionView+DebugDescription.h */; settings = {ATTRIBUTES = (Private, ); }; };
294AC6321DDE4C19002FCE5D /* IGListDiffResultTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 294AC6311DDE4C19002FCE5D /* IGListDiffResultTests.m */; };
294CDE5F1F98E3A7002CF6E4 /* IGListAdapterUpdateListener.h in Headers */ = {isa = PBXBuildFile; fileRef = 294CDE5E1F98E3A6002CF6E4 /* IGListAdapterUpdateListener.h */; settings = {ATTRIBUTES = (Public, ); }; };
294CDE601F995488002CF6E4 /* IGListAdapterUpdateListener.h in Headers */ = {isa = PBXBuildFile; fileRef = 294CDE5E1F98E3A6002CF6E4 /* IGListAdapterUpdateListener.h */; settings = {ATTRIBUTES = (Public, ); }; };
294CDE631F995DD7002CF6E4 /* IGListAdapterUpdateTester.h in Headers */ = {isa = PBXBuildFile; fileRef = 294CDE611F995DD7002CF6E4 /* IGListAdapterUpdateTester.h */; };
294CDE641F995DD7002CF6E4 /* IGListAdapterUpdateTester.m in Sources */ = {isa = PBXBuildFile; fileRef = 294CDE621F995DD7002CF6E4 /* IGListAdapterUpdateTester.m */; };
296AC95C1EA518D3005137E2 /* IGListReloadIndexPath.h in Headers */ = {isa = PBXBuildFile; fileRef = 296AC95A1EA518D3005137E2 /* IGListReloadIndexPath.h */; settings = {ATTRIBUTES = (Private, ); }; };
296AC95D1EA518D3005137E2 /* IGListReloadIndexPath.h in Headers */ = {isa = PBXBuildFile; fileRef = 296AC95A1EA518D3005137E2 /* IGListReloadIndexPath.h */; settings = {ATTRIBUTES = (Private, ); }; };
296AC95F1EA518D3005137E2 /* IGListReloadIndexPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 296AC95B1EA518D3005137E2 /* IGListReloadIndexPath.m */; };
Expand Down Expand Up @@ -460,6 +464,9 @@
292807381E82CE240077A81C /* IGListBatchContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListBatchContext.h; sourceTree = "<group>"; };
294369B01DB1B7AE0025F6E7 /* IGTestNibCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = IGTestNibCell.xib; sourceTree = "<group>"; };
294AC6311DDE4C19002FCE5D /* IGListDiffResultTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListDiffResultTests.m; sourceTree = "<group>"; };
294CDE5E1F98E3A6002CF6E4 /* IGListAdapterUpdateListener.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListAdapterUpdateListener.h; sourceTree = "<group>"; };
294CDE611F995DD7002CF6E4 /* IGListAdapterUpdateTester.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IGListAdapterUpdateTester.h; sourceTree = "<group>"; };
294CDE621F995DD7002CF6E4 /* IGListAdapterUpdateTester.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = IGListAdapterUpdateTester.m; sourceTree = "<group>"; };
296AC95A1EA518D3005137E2 /* IGListReloadIndexPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListReloadIndexPath.h; sourceTree = "<group>"; };
296AC95B1EA518D3005137E2 /* IGListReloadIndexPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListReloadIndexPath.m; sourceTree = "<group>"; };
297278BB1E6B58560099D8EA /* IGListBatchUpdates.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListBatchUpdates.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -632,6 +639,7 @@
0B3B927F1E08D7F5008390ED /* Common */,
0B3B929A1E08D7F5008390ED /* IGListAdapter.h */,
0B3B929B1E08D7F5008390ED /* IGListAdapter.m */,
294CDE5E1F98E3A6002CF6E4 /* IGListAdapterUpdateListener.h */,
0B3B929C1E08D7F5008390ED /* IGListAdapterDataSource.h */,
0B3B929D1E08D7F5008390ED /* IGListAdapterDelegate.h */,
0B3B929E1E08D7F5008390ED /* IGListAdapterUpdater.h */,
Expand Down Expand Up @@ -851,6 +859,8 @@
88144F061D870EDC007C7F66 /* IGTestSupplementarySource.m */,
2995409A1F588C8D00F647CF /* IGTestBindingWithoutDeselectionDelegate.h */,
2995409B1F588C8D00F647CF /* IGTestBindingWithoutDeselectionDelegate.m */,
294CDE611F995DD7002CF6E4 /* IGListAdapterUpdateTester.h */,
294CDE621F995DD7002CF6E4 /* IGListAdapterUpdateTester.m */,
);
path = Objects;
sourceTree = "<group>";
Expand Down Expand Up @@ -925,6 +935,7 @@
989317641E0ED45900DB93B3 /* IGListCompatibility.h in Headers */,
0B3B92FB1E08D7F5008390ED /* IGListAdapterDataSource.h in Headers */,
0B3B92E91E08D7F5008390ED /* IGListIndexSetResultInternal.h in Headers */,
294CDE601F995488002CF6E4 /* IGListAdapterUpdateListener.h in Headers */,
0B3B930B1E08D7F5008390ED /* IGListDisplayDelegate.h in Headers */,
0B3B933F1E08D7F5008390ED /* IGListStackedSectionControllerInternal.h in Headers */,
0B3B93171E08D7F5008390ED /* IGListSectionController.h in Headers */,
Expand Down Expand Up @@ -1002,6 +1013,7 @@
0B3B93441E08D7F5008390ED /* UICollectionView+IGListBatchUpdateData.h in Headers */,
0B3B92DC1E08D7F5008390ED /* IGListMacros.h in Headers */,
0B3B92D61E08D7F5008390ED /* IGListIndexSetResult.h in Headers */,
294CDE5F1F98E3A7002CF6E4 /* IGListAdapterUpdateListener.h in Headers */,
0B3B92C61E08D7F5008390ED /* IGListBatchUpdateData.h in Headers */,
290DF3741E931B57009FE456 /* IGListDebuggingUtilities.h in Headers */,
0B3B92EC1E08D7F5008390ED /* IGListMoveIndexPathInternal.h in Headers */,
Expand Down Expand Up @@ -1041,6 +1053,7 @@
0B3B92FC1E08D7F5008390ED /* IGListAdapterDelegate.h in Headers */,
0B3B92F61E08D7F5008390ED /* IGListAdapter.h in Headers */,
298DD9C21E3ACF4800F76F50 /* IGListBindable.h in Headers */,
294CDE631F995DD7002CF6E4 /* IGListAdapterUpdateTester.h in Headers */,
292807391E82CE240077A81C /* IGListBatchContext.h in Headers */,
0B3B92E21E08D7F5008390ED /* IGListMoveIndexPath.h in Headers */,
297278C41E6B59D50099D8EA /* IGListBatchUpdateState.h in Headers */,
Expand Down Expand Up @@ -1536,6 +1549,7 @@
0B3B92D41E08D7F5008390ED /* IGListIndexPathResult.m in Sources */,
0B3B93361E08D7F5008390ED /* IGListDisplayHandler.m in Sources */,
0B3B92E01E08D7F5008390ED /* IGListMoveIndex.m in Sources */,
294CDE641F995DD7002CF6E4 /* IGListAdapterUpdateTester.m in Sources */,
0B3B93461E08D7F5008390ED /* UICollectionView+IGListBatchUpdateData.m in Sources */,
290DF3551E930C89009FE456 /* IGListDebugger.m in Sources */,
0B3B92E41E08D7F5008390ED /* IGListMoveIndexPath.m in Sources */,
Expand Down
5 changes: 5 additions & 0 deletions Source/IGListAdapter.h
Expand Up @@ -12,6 +12,7 @@
#import <IGListKit/IGListAdapterDataSource.h>
#import <IGListKit/IGListAdapterDelegate.h>
#import <IGListKit/IGListCollectionContext.h>
#import <IGListKit/IGListAdapterUpdateListener.h>

#import <IGListKit/IGListExperiments.h>
#import <IGListKit/IGListMacros.h>
Expand Down Expand Up @@ -261,6 +262,10 @@ NS_SWIFT_NAME(ListAdapter)
- (CGSize)sizeForSupplementaryViewOfKind:(NSString *)elementKind
atIndexPath:(NSIndexPath *)indexPath;

- (void)addUpdateListener:(id<IGListAdapterUpdateListener>)updateListener;

- (void)removeUpdateListener:(id<IGListAdapterUpdateListener>)updateListener;

/**
:nodoc:
*/
Expand Down
33 changes: 30 additions & 3 deletions Source/IGListAdapter.m
Expand Up @@ -20,6 +20,7 @@ @implementation IGListAdapter {
NSMapTable<UICollectionReusableView *, IGListSectionController *> *_viewSectionControllerMap;
// An array of blocks to execute once batch updates are finished
NSMutableArray<void (^)()> *_queuedCompletionBlocks;
NSHashTable<id<IGListAdapterUpdateListener>> *_updateListeners;
}

- (void)dealloc {
Expand Down Expand Up @@ -50,6 +51,7 @@ - (instancetype)initWithUpdater:(id <IGListUpdatingDelegate>)updater

_displayHandler = [IGListDisplayHandler new];
_workingRangeHandler = [[IGListWorkingRangeHandler alloc] initWithWorkingRangeSize:workingRangeSize];
_updateListeners = [NSHashTable weakObjectsHashTable];

_viewSectionControllerMap = [NSMapTable mapTableWithKeyOptions:NSMapTableObjectPointerPersonality | NSMapTableStrongMemory
valueOptions:NSMapTableStrongMemory];
Expand Down Expand Up @@ -336,10 +338,10 @@ - (void)performUpdatesAnimated:(BOOL)animated completion:(IGListUpdaterCompletio
// release the previous items
weakSelf.previousSectionMap = nil;

[weakSelf notifyDidUpdate:IGListAdapterUpdateTypePerformUpdates animated:animated];
if (completion) {
completion(finished);
}

[weakSelf exitBatchUpdates];
}];
}
Expand All @@ -364,7 +366,12 @@ - (void)reloadDataWithCompletion:(nullable IGListUpdaterCompletion)completion {
// purge all section controllers from the item map so that they are regenerated
[weakSelf.sectionMap reset];
[weakSelf updateObjects:newItems dataSource:dataSource];
} completion:completion];
} completion:^(BOOL finished) {
[weakSelf notifyDidUpdate:IGListAdapterUpdateTypeReloadData animated:NO];
if (completion) {
completion(finished);
}
}];
}

- (void)reloadObjects:(NSArray *)objects {
Expand Down Expand Up @@ -398,6 +405,26 @@ - (void)reloadObjects:(NSArray *)objects {
[self.updater reloadCollectionView:collectionView sections:sections];
}

- (void)addUpdateListener:(id<IGListAdapterUpdateListener>)updateListener {
IGAssertMainThread();
IGParameterAssert(updateListener != nil);

[_updateListeners addObject:updateListener];
}

- (void)removeUpdateListener:(id<IGListAdapterUpdateListener>)updateListener {
IGAssertMainThread();
IGParameterAssert(updateListener != nil);

[_updateListeners removeObject:updateListener];
}

- (void)notifyDidUpdate:(IGListAdapterUpdateType)update animated:(BOOL)animated {
for (id<IGListAdapterUpdateListener> listener in _updateListeners) {
[listener listAdapter:self didFinishUpdate:update animated:animated];
}
}


#pragma mark - List Items & Sections

Expand Down Expand Up @@ -1001,10 +1028,10 @@ - (void)performBatchAnimated:(BOOL)animated updates:(void (^)(id<IGListBatchCont
weakSelf.isInUpdateBlock = NO;
} completion: ^(BOOL finished) {
[weakSelf updateBackgroundViewShouldHide:![weakSelf itemCountIsZero]];
[weakSelf notifyDidUpdate:IGListAdapterUpdateTypeItemUpdates animated:animated];
if (completion) {
completion(finished);
}

[weakSelf exitBatchUpdates];
}];
}
Expand Down
55 changes: 55 additions & 0 deletions Source/IGListAdapterUpdateListener.h
@@ -0,0 +1,55 @@
/**
* Copyright (c) 2016-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

#import <Foundation/Foundation.h>

@class IGListAdapter;

NS_ASSUME_NONNULL_BEGIN

/**
The type of update that was performed by an `IGListAdapter`.
*/
NS_SWIFT_NAME(ListAdapterUpdateType)
typedef NS_ENUM(NSInteger, IGListAdapterUpdateType) {
/**
`-[IGListAdapter performUpdatesAnimated:completion:]` was executed.
*/
IGListAdapterUpdateTypePerformUpdates,
/**
`-[IGListAdapter reloadDataWithCompletion:]` was executed.
*/
IGListAdapterUpdateTypeReloadData,
/**
`-[IGListCollectionContext performBatchAnimated:updates:completion:]` was executed by an `IGListSectionController`.
*/
IGListAdapterUpdateTypeItemUpdates,
};

NS_SWIFT_NAME(ListAdapterUpdateListener)
@protocol IGListAdapterUpdateListener <NSObject>

/**
Notifies a listener that the listAdapter was updated.
@param listAdapter The `IGListAdapter` that updated.
@param update The type of update executed.
@param animated A flag indicating if the update was animated. Always `NO` for `IGListAdapterUpdateTypeReloadData`.
@note This event is sent before the completion block in `-[IGListAdapter performUpdatesAnimated:completion:]` and
`-[IGListAdapter reloadDataWithCompletion:]` is executed. This event is also delivered when an
`IGListSectionController` updates via `-[IGListCollectionContext performBatchAnimated:updates:completion:]`.
*/
- (void)listAdapter:(IGListAdapter *)listAdapter
didFinishUpdate:(IGListAdapterUpdateType)update
animated:(BOOL)animated;

@end

NS_ASSUME_NONNULL_END
9 changes: 5 additions & 4 deletions Source/IGListKit.h
Expand Up @@ -26,26 +26,27 @@ FOUNDATION_EXPORT const unsigned char IGListKitVersionString[];
#import <IGListKit/IGListAdapter.h>
#import <IGListKit/IGListAdapterDataSource.h>
#import <IGListKit/IGListAdapterDelegate.h>
#import <IGListKit/IGListAdapterUpdateListener.h>
#import <IGListKit/IGListAdapterUpdater.h>
#import <IGListKit/IGListAdapterUpdaterDelegate.h>
#import <IGListKit/IGListBatchContext.h>
#import <IGListKit/IGListBindable.h>
#import <IGListKit/IGListBindable.h>
#import <IGListKit/IGListBindingSectionController.h>
#import <IGListKit/IGListBindingSectionControllerSelectionDelegate.h>
#import <IGListKit/IGListBindingSectionControllerDataSource.h>
#import <IGListKit/IGListBindable.h>
#import <IGListKit/IGListBindingSectionControllerSelectionDelegate.h>
#import <IGListKit/IGListCollectionContext.h>
#import <IGListKit/IGListCollectionViewLayout.h>
#import <IGListKit/IGListDisplayDelegate.h>
#import <IGListKit/IGListExperiments.h>
#import <IGListKit/IGListGenericSectionController.h>
#import <IGListKit/IGListSectionController.h>
#import <IGListKit/IGListReloadDataUpdater.h>
#import <IGListKit/IGListScrollDelegate.h>
#import <IGListKit/IGListSectionController.h>
#import <IGListKit/IGListSingleSectionController.h>
#import <IGListKit/IGListStackedSectionController.h>
#import <IGListKit/IGListSupplementaryViewSource.h>
#import <IGListKit/IGListUpdatingDelegate.h>
#import <IGListKit/IGListCollectionViewLayout.h>
#import <IGListKit/IGListWorkingRangeDelegate.h>

#endif
Expand Down
1 change: 0 additions & 1 deletion Source/Internal/IGListAdapterInternal.h
Expand Up @@ -62,7 +62,6 @@ IGListBatchContext
@property (nonatomic, strong) NSMutableSet<NSString *> *registeredSupplementaryViewIdentifiers;
@property (nonatomic, strong) NSMutableSet<NSString *> *registeredSupplementaryViewNibNames;


- (void)mapView:(__kindof UIView *)view toSectionController:(IGListSectionController *)sectionController;
- (nullable IGListSectionController *)sectionControllerForView:(__kindof UIView *)view;
- (void)removeMapForView:(__kindof UIView *)view;
Expand Down

0 comments on commit 5cf01cc

Please sign in to comment.