Every node in the treesystem can be thought of as 2 separate components:

Node Metadata

The metadata includes all the bookkeeping information about the node such as:

  • it's name
  • a reference to it's parent node
  • permissions info
  • lastModified date
  • various sync information, such as eTag(s)
  • various crypto information, such as the node's encryptionKey
Node Data

The data is the actual content generated by your app. For example, the serialized version of your object, or the raw bytes of an image file.


The ZeroDark framework automatically downloads the metadata of every node in the treesystem. But it does NOT download any data automatically. You're in complete control of all data downloads.

Thus ZeroDark will tell you about all the nodes that exist in the cloud. So you'll always have a complete picture of the tree/heirarchy, including node names, permissions, lastModified, etc. But you get to decide if/when you download the actual data (node content). This allows you to optimize for your app. For example:

  • speed up new app logins by not downloading old content
  • save disk space by deleting local copies of node data that are no longer being used
  • optimize per-device by downloading certain content on demand



The framework makes use of several delegate calls to inform you about various discovery events:

/// ZeroDark has just discovered a new node in the cloud.
func didDiscoverNewNode(_ node: ZDCNode, at path: ZDCTreesystemPath, transaction: YapDatabaseReadWriteTransaction) {

/// ZeroDark has just discovered a modified node in the cloud.
func didDiscoverModifiedNode(_ node: ZDCNode, with change: ZDCNodeChange, at path: ZDCTreesystemPath, transaction: YapDatabaseReadWriteTransaction) {

/// ZeroDark has just discovered a moved/renamed node.
func didDiscoverMovedNode(_ node: ZDCNode, from oldPath: ZDCTreesystemPath, to newPath: ZDCTreesystemPath, transaction: YapDatabaseReadWriteTransaction) {

/// ZeroDark has just discovered a deleted node.
func didDiscoverDeletedNode(_ node: ZDCNode, at path: ZDCTreesystemPath, timestamp: Date?, transaction: YapDatabaseReadWriteTransaction) {



What to download, and when to download it will be app specific. And you can structure your nodes & treesystem to optimize your apps performance.

When you're ready to download a node's content, you can use the DownloadManager:

let options = ZDCDownloadOptions()
options.canDownloadWhileInBackground = true

zdc.downloadManager!.downloadNodeData( node,
                              options: options,
                      completionQueue: DispatchQueue.global())
{ (cloudDataInfo: ZDCCloudDataInfo?, cryptoFile: ZDCCryptoFile?, error: Error?) in

  if let cryptoFile = cryptoFile {

    do {
      // Read & decrypt the file.
      // Since this is a small file, we can just decrypt it into memory.
      // Note: We're already executing in a background thread (DispatchQueue.global).
      // So it's fine if we read from the disk in a synchronous fashion here.
      let cleartext = try ZDCFileConversion.decryptCryptoFile(intoMemory: cryptoFile)

      // Process it
      processDownloadedTask(cleartext, forNodeID: nodeID)

    } catch {}

    // Cleanup: Delete the downloaed file from disk
    // unless we asked the DiskManager to manage it.

The DownloadManager automatically coalesces multiple requests for the same item. So you don't have to worry about duplicated downloads.


Image Thumbnails

As discussed in the push article, you can provide thumbnails for large images. This allows other devices to download the thumbnail independently of the full large image. Which makes your app appear much speedier.

Further, the ImageManager provides a streamlined process for downloading & caching node thumbnails. It looks like this:

let preFetch = {(image: UIImage?, willFetch: Bool) in

  // This closure is invoked BEFORE the fetchNodeThumbnail() function returns.
  cell.thumbnail.image = image ?? defaultImage

let postFetch = {(image: UIImage?, error: Error?) in

  // This closure is invoked LATER, after the download or disk-read has completed.
  if cell.taskID == taskID {
    cell.thumbnail.image = image ?? defaultImage
  } else {
    // The cell has been recycled. Ignore.

zdc.imageManager?.fetchNodeThumbnail(imageNode, preFetch: preFetch, postFetch: postFetch)

Here's what the fetchNodeThumbnail function does for you:

  • checks to see if there's a cached version of the thumbnail in memory
  • if so, its immediately returned (via the preFetch closure)
  • otherwise it invokes the preFetch closure, and sets the willFetch parameter to true
  • then it checks the DiskManager to see if we have a cached version on disk
  • if so, it will load the file from disk asynchronously, and invoke the postFetch with the loaded image
  • otherwise it will download the thumbnail using the DownloadManager
  • when the download completes, the image will get cached in the DiskManager
  • and the downloaded image will get sent to you via the postFetch closure

Next Step

Now that you have an idea of how pulling & downloading works, dive deeper by walking through the download tutorial.