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

[ASCollectionNode] Add -isProcessingUpdates and -onDidFinishProcessingUpdates: APIs. #522

Merged
merged 6 commits into from
Aug 23, 2017

Conversation

appleguy
Copy link
Member

Over time, there have actually been a lot of legitimate uses for an API like this.
In fact, I'm not quite sure what has held us back from adding one!

I believe that at least some portion of -wait calls (even if less than 50%) could be
replaced with -onDidFinishProcessingUpdates:, which could potentially improve the
performance of applications using -wait by a significant amount.

Please take a close look at implementation correctness. Although I'm in a bit of a
rush, I'm aiming to make this properly documented and added a basic test -- but it
could certainly use some more detailed testing as a followup.

@appleguy
Copy link
Member Author

This could be used to quite easily fix this ASBatchContext edge case: #521

…gUpdates: APIs.

Over time, there have actually been a lot of legitimate uses for an API like this.
In fact, I'm not quite sure what has held us back from adding one!

I believe that at least some portion of -wait calls (even if less than 50%) could be
replaced with -onDidFinishProcessingUpdates:, which could potentially improve the
performance of applications using -wait by a significant amount.

Please take a close look at implementation correctness. Although I'm in a bit of a
rush, I'm aiming to make this properly documented and added a basic test -- but it
could certainly use some more detailed testing as a followup.
@ghost
Copy link

ghost commented Aug 20, 2017

🚫 CI failed with log

@appleguy
Copy link
Member Author

I see one of the new tests is failing on the build server, although it doesn't fail on my local machine - even trying 9.3, 10.0, and 10.3.1 simulators

I'll wait for the last build to finish and then will start debugging-by-commit-pushing :).

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 feature has been a long time coming. More transparency around the state of the collection node means (1) more options during development and (2) more developer understanding of the async nature of collection node.

Comments that merit no more than 10 minutes of work in my estimation.

*
* This method will always return NO if called immediately after -waitUntilAllUpdatesAreProcessed.
*/
- (BOOL)isProcessingUpdates;
Copy link
Member

Choose a reason for hiding this comment

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

Trivial: Make this a readonly property for better swift interop?

Copy link
Member Author

Choose a reason for hiding this comment

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

Great suggestion, I'll try to remember this in the future (so foreign to me :-P)

{
ASDisplayNodeAssertMainThread();
long waitResult = dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_NOW);
return (waitResult == 0 ? NO : YES);
Copy link
Member

Choose a reason for hiding this comment

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

Is it possible that we could return a false NO if they call this in between the time the editing transaction group is finished and the main queue services the data controller's UIKit flush?

We could either flush the main serial queue (but not the editing transaction queue) or add API to check if the main serial queue is empty.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, this is a good catch. I think this might be possible.

If we flush the main serial queue here, you could get situations where calling isProcessingUpdates results in calls out to the delegate / dataSource, which would be pretty weird and maybe unexpected by clients. So I think I'll just check the length of the queue.

Copy link
Member Author

Choose a reason for hiding this comment

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

@Adlai-Holler do you think we should perform a setNeedsLayout in the case that the queues are drained, before returning NO? This would technically be needed to ensure all information is flushed to / reflected in the UICollectionViewLayout...

Copy link
Member

Choose a reason for hiding this comment

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

Are you maybe thinking about layoutIfNeeded? That would absolutely make sense 👍

});
});
}
}
Copy link
Member

Choose a reason for hiding this comment

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

A couple low-impact notes on this method:

  • If we're asserting main, is it worth removing ASPerformBlockOnMainThread? Presumably an error of this kind can be reliably caught during development. If the notion here is "better safe than sorry," I'm down with that as well.
  • Should we schedule the retry on the main serial queue, so that if they wait on updates to complete we can be sure this block is run before returning from the wait? I suppose it's not a big deal – there's no guarantee this block will be run as soon as we finish processing updates. But at the same time, this is a break from the usual pattern of editingQueue -> main serial queue for main-thread-injection into data controller's async work.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, the idea is some extra safety in the case the assertion fails in production. It is not likely to be necessary, but the semantics of PerformOnMain are clear enough that it seems OK.

Interested in your input on the other point. I had originally implemented it as waiting on the mainSerialQueue instead of a dispatch.

I was thinking (and now believe this may be incorrect) that doing it this way would allow us to call out to the blocks in an "intermediate quiesced" state to perform some operations before potentially more edits start getting processed.

However with the changes to isProcessingUpdates, I think this is actually invalid and we should schedule on the serial queue.

Copy link
Member Author

Choose a reason for hiding this comment

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

@Adlai-Holler is it correct that we do NOT perform using the _editingTransactionGroup? I don't want multiple scheduled blocks to get stuck waiting for each other (as they do schedule in the main serial queue, and so I think this is correct, but I'm not completely confident.

Copy link
Member

Choose a reason for hiding this comment

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

That's right! The transaction group is only (and always) applied to work blocks on the editing transaction queue.

// Wait for ASDK reload to finish
[cn waitUntilAllUpdatesAreCommitted];

XCTAssertTrue(!cn.isProcessingUpdates, @"ASCollectionNode should no longer be processing updates after -wait call");

Copy link
Member

Choose a reason for hiding this comment

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

💕

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.

Will accept as soon as you address the locking concern in ASMainSerialQueue.

@@ -40,6 +40,11 @@ - (instancetype)init
return self;
}

- (NSUInteger)numberOfScheduledBlocks
{
return _blocks.count;
Copy link
Member

Choose a reason for hiding this comment

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

I think we need to grab _serialQueueLock lock here.

{
ASDisplayNodeAssertMainThread();
if (_mainSerialQueue.numberOfScheduledBlocks > 0) {
return YES;
Copy link
Member

Choose a reason for hiding this comment

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

👍

- (void)reloadDataImmediately ASDISPLAYNODE_DEPRECATED_MSG("Use -reloadData / -reloadDataWithCompletion: followed by -waitUntilAllUpdatesAreProcessed instead.");

// TODO: Rename framework uses of this method and add deprecation message.
// ASDISPLAYNODE_DEPRECATED_MSG("This method has been renamed to -waitUntilAllUpdatesAreProcessed.");
Copy link
Member

Choose a reason for hiding this comment

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

If you're not going to do this in this PR, make sure to create an issue to track it.

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.

Good to go now!

…ngUpdates:. Rename -waitUntil to consistent naming.
@appleguy
Copy link
Member Author

@nguyenhuy @Adlai-Holler thanks for the great feedback! I went ahead and renamed the method from Committed to Processing everywhere except the *View classes, where a deprecation warning already exists and now directs people to the Processing version (it seemed silly to add a second, also deprecated name).

This change makes me realize that we should give AS{Collection,Table}Node access to the ASDataController directly. There are a number of APIs where no participation from the View class is required. Even without breaking support for using the View classes independently (which is becoming less and less important), calls like isProcessingUpdates or waitUntil.. could be passed directly from the Node to ASDataController.

@nguyenhuy
Copy link
Member

@appleguy: Yes, agree that the nodes should have direct access to data controller. In a far future, we may even tell the data controller to do the initial load before the view is even allocated. Definitely possible with an async collection layout.

@nguyenhuy
Copy link
Member

ok, let's get this in!

@nguyenhuy nguyenhuy merged commit ccc5786 into master Aug 23, 2017
@nguyenhuy nguyenhuy deleted the ProcessingUpdates branch August 23, 2017 10:16
@appleguy
Copy link
Member Author

Thanks Huy!

bernieperez pushed a commit to AtomTickets/Texture that referenced this pull request Apr 25, 2018
…gUpdates: APIs. (TextureGroup#522)

* [ASCollectionNode] Add -isProcessingUpdates and -onDidFinishProcessingUpdates: APIs.

Over time, there have actually been a lot of legitimate uses for an API like this.
In fact, I'm not quite sure what has held us back from adding one!

I believe that at least some portion of -wait calls (even if less than 50%) could be
replaced with -onDidFinishProcessingUpdates:, which could potentially improve the
performance of applications using -wait by a significant amount.

Please take a close look at implementation correctness. Although I'm in a bit of a
rush, I'm aiming to make this properly documented and added a basic test -- but it
could certainly use some more detailed testing as a followup.

* [ASCollectionNode] Improvements to the implementation of -isProcessingUpdates and -onDidFinishProcessingUpdates:

* Add lock to ASMainSerialQueue count method.

* [ASTableNode] Implement -isProcessingUpdates and -onDidFinishProcessingUpdates:. Rename -waitUntil to consistent naming.
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