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
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 52 additions & 1 deletion docs/_docs/development/node-lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,55 @@ layout: docs
permalink: /development/node-lifecycle.html
---

<p>👷👷‍♀️Under construction…</p>
# At a glance

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?


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

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".


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?


Furthermore, the current implementations of ASCollectionNode and ASTableNode allocate all cell nodes as soon as they are inserted. That is, if clients perform a batch update that inserts 100 items into a collection node, the collection node will allocate 100 cell nodes as part of the processing of the batch update. It will also perform a layout calculation on each of the new cell nodes. Thus, at the end of the process, the collection node will manage 100 nodes more and these nodes have calculated layouts ready to be used.

Because of the above behavior, it can take a while for ASCollectionNode and ASTableNode to process a batch update that inserts a large number of items. In such cases, it's recommended to use the "node block" API that allows cell nodes to be allocated in parallel off the main thread (instead of serially on the main thread) and, if performance is still a concern, use the batch fetching API to split up your data set and gradually expose more data to the container node as end users scroll.

For implementation details, look into ASDataController. `-updateWithChangeSet:` is the entry point and often a good place to start. ASDataController is also the only class that has strong references to all cell nodes at any given time.

## ASCollectionLayout
To address the downsides of handling a large data set mentioned above, we introduced a new set of APIs for ASCollectionNode that allows it to lazily allocate and layout cell nodes as users scroll. However, due to certain limitations, this new functionality is only applicable to collection layouts that know the size of each and every cell. Examples of such layouts include photo gallery, carousel, and paging layouts.

For more details, look into ASCollectionLayout, ASCollectionGalleryLayoutDelegate and ASCollectionFlowLayoutDelegate.

## 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.


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?


## ASDeallocQueue

As mentioned above, ASDeallocQueue helps to defer the deallocation of objects given to it by increasing the reference count of each object -- essentially retaining them and acting as their sole owner -- and then release them later on a background thread.

# Nodes that are not managed by containers

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.
Copy link
Member

Choose a reason for hiding this comment

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

…"the parent node retain it" -> …"the parent node retains it"

…"directly in case of ASViewController"-> …"directly in the case of ASViewController"


## 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?


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?


For more details, look into the implementation of ASDataController, particularly `-_updateVisibleNodeIndexPaths`.

# 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?


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"


It's generally safe to think that the backing store will be deallocated as soon as the node is deallocated. When it comes to implementation details, the store can be deallocated a bit later than the node. This happens when the node is deallocated off the main thread and the view/layer must be deallocated on the main thread, so ASDisplayNode has to dispatch to the main thread and then discards its view/layer then. Another case in which the backing store is deallocated later is when developers retain it themselves and won't let go at a later time.