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

[Dev docs] Node Lifecycle #1536

Merged
merged 3 commits into from
Jun 20, 2019
Merged

[Dev docs] Node Lifecycle #1536

merged 3 commits into from
Jun 20, 2019

Conversation

nguyenhuy
Copy link
Member

Here is my draft on a new documentation aims at people interested in learning about technical details of the lifecycle of ASDisplayNode, as well as its interface and loading states. Hopefully it'd be useful for others who want to ramp up and hopefully start contributing to the framework.

Let me know if there are other topics I should cover, or anything I should explain more/dig deeper, or something that is wrong/no longer valid (imposter syndrome much!). Cheers!

Here is my draft on a new documentation aims at people interested in learning about technical details of the lifecycle of ASDisplayNode, as well as its interface and loading states. Hopefully it'd be useful for others who want to ramp up and hopefully start contributing to the framework.

Let me know if there are other topics I should cover, or anything I should explain more/dig deeper, or something that is wrong/no longer valid (imposter syndrome much!). Cheers!
Copy link
Contributor

@bolsinga bolsinga left a comment

Choose a reason for hiding this comment

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

This looks terrific. Thanks for creating it. I learned more about Texture.

# Nodes managed by <a href = "http://texturegroup.org/docs/containers-overview.html">node containers</a>

Node containers are responsible for the lifecycle of nodes that it manages. Generally speaking, node containers allocate their nodes as soon as needed and release them when they are no longer useful. Texture assumes that node containers fully manage their nodes and expects clients to not retain these nodes and/or modify their lifecycles. For example, clients should not attempt to store instances of ASCellNodes allocated by ASCollectionNode, ASPagerNode (which is a thin subclass of ASCollectionNOde) or ASTableNode as an attempt to reuse them later.
Copy link
Contributor

Choose a reason for hiding this comment

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

ASCollectionNOde -> ASCollectionNode

Copy link
Member

@garrettmoon garrettmoon left a comment

Choose a reason for hiding this comment

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

Thank you so much Huy!

Texture uses ARC (Automatic Reference Counting) and thus objects are deallocated as soon as they are no longer strongly referenced. When it comes to instances of ASDisplayNode and its subclasses, different kinds of nodes have different lifecycles and there are benefits in understanding their lifecycles as well as when they enter interface and loading states, so keep reading.

# Nodes managed by <a href = "http://texturegroup.org/docs/containers-overview.html">node containers</a>
Copy link
Member

Choose a reason for hiding this comment

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

Can this be a relative link?

# Nodes managed by <a href = "http://texturegroup.org/docs/containers-overview.html">node containers</a>

Node containers are responsible for the lifecycle of nodes that it manages. Generally speaking, node containers allocate their nodes as soon as needed and release them when they are no longer useful. Texture assumes that node containers fully manage their nodes and expects clients to not retain these nodes and/or modify their lifecycles. For example, clients should not attempt to store instances of ASCellNodes allocated by ASCollectionNode, ASPagerNode (which is a thin subclass of ASCollectionNOde) or ASTableNode as an attempt to reuse them later.
Copy link
Member

Choose a reason for hiding this comment

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

"nodes they manage" instead of "nodes that it manages".

Node containers are responsible for the lifecycle of nodes that it manages. Generally speaking, node containers allocate their nodes as soon as needed and release them when they are no longer useful. Texture assumes that node containers fully manage their nodes and expects clients to not retain these nodes and/or modify their lifecycles. For example, clients should not attempt to store instances of ASCellNodes allocated by ASCollectionNode, ASPagerNode (which is a thin subclass of ASCollectionNOde) or ASTableNode as an attempt to reuse them later.

ASCollectionNode and ASTableNode allocate ASCellNodes as soon as they are added to the container node, either via reload data or insertions as part of a batch update. Similar to UICollectionView/UITableView, the first initial data load is basically a reload data without a previous data set. Unlike UICollectionView and UITableView where cells are reused and reconfigured before they come onscreen and therefore the number of UICollectionViewCells or UITableViewCells at any given time is fewer than the number of items or rows inserted, ASCollectionNode and ASTableNode do not reuse ASCellNodes. As a result, the number of ASCellNodes managed by the collection or table node is exactly the same as the number of items or rows inserted up to that time (i.e barring any pending batch updates that have not yet been consumed).
Copy link
Member

Choose a reason for hiding this comment

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

Instead of

Unlike UICollectionView and UITableView where cells are reused and reconfigured before they come onscreen and therefore the number of UICollectionViewCells or UITableViewCells at any given time is fewer than the number of items or rows inserted, ASCollectionNode and ASTableNode do not reuse ASCellNodes.

Unlike UICollectionView and UITableView where cells are reused and reconfigured before they come onscreen, ASCollectionNode and ASTableNode do not reuse ASCellNodes.

I'm not sure the parenthetical bit about UI* having fewer cells is any more illuminating given the rest of the context?

## Deallocation of ASCellNodes

As mentioned above, since ASCellNodes are not meant to be reused, they have a longer lifecycle compare to the view or layer that they encapsulate or their corresponding UICollectionViewCell or UITableViewCell. ASCellNodes are deallocated when they are no longer used. Such condition happens when the cells are removed from the container node: after a batch update that includes a reload data or deletion, or after the container node is no longer used and thus released.
Copy link
Member

Choose a reason for hiding this comment

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

compare -> compared

Maybe:
ASCellNodes are deallocated when they are no longer used. Such condition happens when the cells are removed from the container node: after a batch update that includes a reload data or deletion, or after the container node is no longer used and thus released.

Should be:
ASCellNodes are deallocated when they are no longer used and removed from the container node. This can occur after a batch update that includes a reload data or deletion, or after the container node is no longer used and thus released.

As mentioned above, since ASCellNodes are not meant to be reused, they have a longer lifecycle compare to the view or layer that they encapsulate or their corresponding UICollectionViewCell or UITableViewCell. ASCellNodes are deallocated when they are no longer used. Such condition happens when the cells are removed from the container node: after a batch update that includes a reload data or deletion, or after the container node is no longer used and thus released.

For the latter case in which the container node is no longer used and thus released, their cell nodes are not released immediately. That is because collection and table nodes might hold a large number of cell nodes and releasing all of them (and their subnodes, more on this later) at the same time can cause a noticeable delay. To avoid that, ASCollectionNode and ASTableNode release their instance variables, most noticeably an ASDataController instance, on a background thread using a helper called ASDeallocQueue. Since the ASDataController instance is the true owner of all cell nodes -- it has a strong reference to all of them --, all of those cell nodes are deallocated off the main thread as well. As a result, you can expect a delay from which a collection or table view is released until all the cell nodes are fully released and their memory reclaimed.
Copy link
Member

Choose a reason for hiding this comment

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

Maybe add:

It's important to remember this when debugging memory leaks: objects referenced by the data controller may take a bit to show as dealloced by Instruments.

Copy link
Member

Choose a reason for hiding this comment

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

Or add it in the next paragraph and mention the dealloc queue instead of the data controller?

These are nodes that are often directly created by client code, such as direct and indirect subnodes of cell nodes. When a node is added to a parent node, the parent node retain it until it's removed from the parent node, or until the parent node is deallocated. As a result, if the subnode is not retained by client code in any other way or if it's not removed from the parent node, the subnode's lifecycle is tied to the parent node's lifecycle. In addition, since nodes often live in a hierarchy, the entire node hierarchy has the same lifecycle as the root node's. Lastly, if the root node is managed by a node container -- directly in case of ASViewController and the like, or indirectly as a cell node of a collection or table node --, then the entire node hierarchy is managed by the node container.

## Node lifecycle under <a href = "http://texturegroup.org/docs/automatic-subnode-mgmt.html">Automatic Subnode Management (ASM)</a>
Copy link
Member

Choose a reason for hiding this comment

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

Relative link?

## Node lifecycle under <a href = "http://texturegroup.org/docs/automatic-subnode-mgmt.html">Automatic Subnode Management (ASM)</a>

ASM allows clients to manipulate the node hierarchy by simply returning layout specs that contain the only subnodes needed by a parent node at a given time. Texture then calculates subnode insertions and removals by looking at the previous and current layout specs and updates the node hierarchy accordingly. To support animation between the two layout specs, subnodes insertions and removals are performed at different times. New subnodes are inserted at the beginning of the animation so that they are present in the view hierarchy and ready for the upcoming animation. As a result, new subnodes are retained by the parent node at the beginning. Old subnodes, however, are removed after the animation finishes. If the old subnodes are not retained anywhere else, then they'll be released at that time.
Copy link
Member

Choose a reason for hiding this comment

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

"To support animation between the two layout specs, subnodes insertions"… -> "To support animation between the two layout specs, subnode insertions"…

# Node interface states

With the support of <a href = "http://texturegroup.org/docs/intelligent-preloading.html">Intelligent Preloading</a>, ASDisplayNode has three interface states: Preload, Display and Visible. These states are fully utilizied when a node is managed by an ASTableView or ASCollectionView, either directly as an ASCellNode or indirectly as a subnode of an ASCellNode. Both ASTableView and ASCollectionView use ASRangeController to determine the state of each ASCellNode they manage and recursively set the state to every node in the hierarchy.
Copy link
Member

Choose a reason for hiding this comment

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

relative link?

# Node loading state

As an abstraction around a backing store (i.e a view or a layer), ASDisplayNodes often outlived their backing store. That is, a node may not have loaded its view or layer at a given time. ASDisplayNode loads its backing store the first time the store is accessed. When the `-view` or `-layer` getter is called on a node, it checks if its view or layer is ready and allocates it if needs to. Once a node is loaded, it never unloads. As a result, once loaded, the backing store survives as long as the node itself. This is true for all nodes, including ASCellNodes. When it's time to display an ASCellNode, the cell node simply attaches its view to the provided UICollectionViewCell or UITableViewCell and reconfigures some of its properties accordingly.
Copy link
Member

Choose a reason for hiding this comment

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

Instead of backing store, maybe backing view or layer?

As an abstraction around a backing store (i.e a view or a layer), ASDisplayNodes often outlived their backing store. That is, a node may not have loaded its view or layer at a given time. ASDisplayNode loads its backing store the first time the store is accessed. When the `-view` or `-layer` getter is called on a node, it checks if its view or layer is ready and allocates it if needs to. Once a node is loaded, it never unloads. As a result, once loaded, the backing store survives as long as the node itself. This is true for all nodes, including ASCellNodes. When it's time to display an ASCellNode, the cell node simply attaches its view to the provided UICollectionViewCell or UITableViewCell and reconfigures some of its properties accordingly.

Since allocating the backing store incurs costs time and memory, it's recommended to only done when absolutely needed. One common mistake developers usually make is accessing the backing store right after initializing a node, for example, to set some properties on the store. This is referred to as "premature view allocation". Instead, such configuration should be done in `-viewDidLoad`.
Copy link
Member

Choose a reason for hiding this comment

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

…"it's recommended to only done when" -> …"it's recommended to only access the node's view or layer when"

@nguyenhuy
Copy link
Member Author

Thanks for reviewing! Let's land this diff and iterate over if need to.

@nguyenhuy nguyenhuy merged commit 533c7eb into master Jun 20, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants