Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ASCollectionView] Add delegate bridging and index space translation for missing UICollectionViewLayout properties. #440

Merged
merged 3 commits into from
Jul 17, 2017

Conversation

appleguy
Copy link
Member

This is a first attempt at resolving #438.

I know there is already one similar method to do indexPath conversions, and that the #define for
accessing properties on the UICollectionViewFlowLayout should probably be moved somewhere else.

Looking for feedback on the general direction here. In particular, I'm a bit surprised that so much
has changed in how these calls occur, as I believe especially constrainedSizeForNodeAtIndexPath: is
now called with inconsistent index path spaces (but was not before this PR landed in March:
https://github.com/facebookarchive/AsyncDisplayKit/pull/3136/files)

Since the impact of mixing the spaces is fairly serious (can cause crashes), I'm also wondering if
I am misinterpreting some aspects of the code, or if maybe the crashing impact wasn't noticed yet.

…for missing UICollectionViewLayout properties

This is a first attempt at resolving #438.

I know there is already one similar method to do indexPath conversions, and that the #define for
accessing properties on the UICollectionViewFlowLayout should probably be moved somewhere else.

Looking for feedback on the general direction here. In particular, I'm a bit surprised that so much
has changed in how these calls occur, as I believe especially constrainedSizeForNodeAtIndexPath: is
now called with inconsistent index path spaces (but was not before this PR landed in March:
https://github.com/facebookarchive/AsyncDisplayKit/pull/3136/files)

Since the impact of mixing the spaces is fairly serious (can cause crashes), I'm also wondering if
I am misinterpreting some aspects of the code, or if maybe the crashing impact wasn't noticed yet.
@ghost
Copy link

ghost commented Jul 14, 2017

🚫 CI failed with log

Copy link
Member

@nguyenhuy nguyenhuy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it is indeed a problem. It's never safe to ask the data controller using an index path in UIKit space. The change in this PR looks safe to me.

I think we need to have new GitHub issues to track and discuss each of the followings:

  • Have a mechanism in place that can detect and report illegal index-space crossings. I'm not sure, but it might be as simple as having an NSIndexPath wrapper that contains extra information regarding the index space.
  • Do an audit on the current code base and fix problems similar to this.
  • Discuss a new API for invalidating size ranges.

@Adlai-Holler Interested in your thoughts too.

@ghost
Copy link

ghost commented Jul 14, 2017

🚫 CI failed with log

@@ -949,7 +955,8 @@ - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSe
return [_dataController.visibleMap numberOfItemsInSection:section];
}

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)layout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you changed the parameter name from collectionViewLayout to layout, you need to update this line.

if (supplementaryKind == nil) {
if (indexPath == nil) {
// In this case, the latest data no longer has the element, so we can't ask the delegate for its latest constrained size.
// The element is still visible, but will be deleted soon. We can reuse the last-known constrainedSize for the element.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The element is still visible

"visible" is quite misleading here, and so does the "vibisbleMap" name. We should change it to "current" or something similar that indicates that the element is still recognized by UIKit objects.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. I agree that if we clarified the terminology here, and used it consistently everywhere (and we could even start putting it in method names, or comments at the start of methods), it would be easier to ensure correctness.

Ideas for "UIKit" space:

  • View
  • UIKit
  • Committed

Ideas for "Latest" space:

  • Source
  • App
  • DataSource
  • Latest

I feel like there must be a better, more parallel set of names. One trouble with "current" is that you could imagine that being the current UIKit state, or the current App state.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UIDataSourceTranslating uses "Presentation." I kind of like that name.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"DataSource" and "Presentation", or "Processed" and "Presenting"?

@appleguy
Copy link
Member Author

@nguyenhuy I'm really glad you mentioned the NSIndexPath wrapper idea. This is exactly what I had in mind, too. We should make the data types incompatible to cross the index-space boundary!

@Adlai-Holler I'd like your feedback on this, but I think we should at least consider using a simple struct (exactly like ASIndexPath) for this purpose. Index path handling remains one of our biggest costs, including specifically main thread costs during large collection update application cycles that are the periods of time where we see frame drops.

There might be an easier way to distinguish between the index spaces using NSIndexPath (although I'm hesitant to use an associated value, even a non-object integer). That said, it would be interesting to consider what the "best" way is to represent these values if we weren't tied to NSIndexPath.

@Adlai-Holler
Copy link
Member

@appleguy @nguyenhuy I'm opposed to migrating to a struct because on 64-bit, NSIndexPath is a tagged pointer, and it's absolutely ubiquitous when dealing with UIKit.

To prevent illegal index space crossings, I believe the best solution is for us to modify our APIs not to use index paths so often. For example, this delegate method should probably be changed to constrainedSizeForNode: and you can read the current data-source-indexpath, supplementary element kind, and visible-indexpath safely on your end if it's relevant.

UIKit is restricted always to using NSIndexPaths because their API doesn't reify elements. We do. We have ASCollectionElement and ASCellNode which both serve as better identifiers for content than index paths, and from which we can always derive the appropriate index path in a desired space.

@appleguy
Copy link
Member Author

@Adlai-Holler - ok, good points. Keeping NSIndexPath where we need to use index-based references at all.

I think we are all supportive of using elements as widely as possible. However, we still need a way to keep the index spaces straight and physically prevent / assert against problems like the changing behavior of the constrainedSizeForNode: call (since this had been in the codebase for a while, it's an indication that some kind of assertion would've helped us catch it much sooner).

Another challenge with relying on elements is that some APIs just need to use indexPaths. This includes UICollectionView passthrough. Although we shouldn't let that influence the node-based API, the technical requirements of keeping the index spaces straight are quite limited because of how great the changeSet / elementMap infrastructure is, allowing translation between them. So just like the tweaks in this patch, making sure the delegate gets sane values is more a matter of verifying correctness than a tough implementation challenge.

Any feedback on this? Would an assertion to guard our assumptions be useful?

Copy link
Member

@Adlai-Holler Adlai-Holler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't beautiful, but it improves the reliability of using volatile data sources with flow layouts so I think we should roll with it. Thanks @appleguy for building this patch and following through on it.

NSIndexPath *sectionIndexPath = [NSIndexPath indexPathForItem:0 inSection:section];
ASCollectionElement *element = [_dataController.visibleMap elementForItemAtIndexPath:sectionIndexPath];
NSIndexPath *pendingIndexPath = element ? [_dataController.pendingMap indexPathForElement:element] : nil;
return pendingIndexPath;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be - (NSInteger)dataSourceSectionForSection:(NSInteger)section? I could see not-checking-NSNotFound on the caller side being a problem. Not checking nil is roughly as likely to occur but slightly less likely to cause issues immediately.

Currently ASSection objects are only created if the user implements the optional contextForSection: API. We should instead create ASSections unconditionally and use them as identifiers, similar to ASCollectionElement. That way looking up the indexes of sections between maps is easy!!

if (supplementaryKind == nil) {
if (indexPath == nil) {
// In this case, the latest data no longer has the element, so we can't ask the delegate for its latest constrained size.
// The element is still visible, but will be deleted soon. We can reuse the last-known constrainedSize for the element.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UIDataSourceTranslating uses "Presentation." I kind of like that name.

@nguyenhuy
Copy link
Member

nguyenhuy commented Jul 14, 2017

Regarding preventing index space crossings, although I totally understand the reasons behind, I'm not so keen on changing the API. For the time being, adding assertions sounds fine, as long as those assertions always trigger, as opposed to trigger only after some race conditions (e.g different visible and pending maps).

@appleguy
Copy link
Member Author

appleguy commented Jul 14, 2017 via email

@appleguy
Copy link
Member Author

@nguyenhuy @Adlai-Holler I found another method that needs index-space conversion (layout:sizeForItem: itself). I have a fix for this locally, but haven't updated this patch yet.

Thanks for the feedback here. I need to focus on fixing locally to address a release in flight. Feel free to continue landing diffs (although I would appreciate if we can hold off for a few days on any non-functional changes such as naming updates) and I will check which changes are still needed once rebasing.

@appleguy
Copy link
Member Author

@nguyenhuy @Adlai-Holler I worked on this throughout the weekend, and it's pretty heavily tested now. If it passes your review, feel free to merge it. Thanks for your help!

} else {
sizeRange = [self dataController:_dataController constrainedSizeForSupplementaryNodeOfKind:supplementaryKind atIndexPath:indexPath];
return [node layoutThatFits:element.constrainedSize].size;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so we no longer reach out to the delegate for a size range but rely on element.constrainedSize. This seems like a simpler approach 👍

It also means once a size range is returned by the delegate, it's no longer mutable by clients. They have to reload the element to change it, or request for a new API which simply exposes -[ASDataController relayoutNodes:nodesSizeChanged:]. Until then, let's get this rolling.

@nguyenhuy nguyenhuy merged commit 5115f66 into master Jul 17, 2017
bernieperez pushed a commit to AtomTickets/Texture that referenced this pull request Apr 25, 2018
…for missing UICollectionViewLayout properties. (TextureGroup#440)

* [ASCollectionView] Add delegate bridging and index space translation for missing UICollectionViewLayout properties

This is a first attempt at resolving TextureGroup#438.

I know there is already one similar method to do indexPath conversions, and that the #define for
accessing properties on the UICollectionViewFlowLayout should probably be moved somewhere else.

Looking for feedback on the general direction here. In particular, I'm a bit surprised that so much
has changed in how these calls occur, as I believe especially constrainedSizeForNodeAtIndexPath: is
now called with inconsistent index path spaces (but was not before this PR landed in March:
https://github.com/facebookarchive/AsyncDisplayKit/pull/3136/files)

Since the impact of mixing the spaces is fairly serious (can cause crashes), I'm also wondering if
I am misinterpreting some aspects of the code, or if maybe the crashing impact wasn't noticed yet.

* [ASCollectionView] Cleanup and final changes for UIKit passthrough fixes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants