肘子的 Swift 记事本

Advanced iCloud Documents: Understanding Placeholder Files, Space Optimization, and Operational Techniques

Published on

Get weekly handpicked updates on Swift and SwiftUI!

Welcome back to our in-depth discussion on iCloud Documents. In the previous article In-Depth Guide to iCloud Documents: Fundamental Setup and File Operations, we explored the basic concepts of iCloud Documents, the setup process, and basic file operations. This article will continue to delve into the topic based on the previous information, so if you haven’t read the previous article yet, we recommend familiarizing yourself with the fundamentals first in order to better understand the content of this article.

In this article, we will discuss the unique characteristics of the iCloud Documents folder, the importance and applications of placeholder files, and we will also explore tips and techniques related to file operations and debugging.

iCloud Documents Folder

Although both are folders accessible by applications, the iCloud Documents folder has several notable differences compared to the folders within the app sandbox (such as Documents, Application Support, etc.):

  • The iCloud Documents folder is not within the app’s sandbox scope; it is located in a special position in the file system and is isolated from the app sandbox.
  • Some files in the iCloud Documents folder may be visible or shareable to other applications, while data within the app is typically private.
  • Files in the iCloud Documents folder are automatically synchronized with iCloud to support document sharing between Apple devices. Whether files within the app sandbox are synchronized with iCloud depends on the app’s settings (such as enabling cloud backup), file location (Documents is synchronized by default), and file configuration (files in the Application Support directory can be excluded from synchronization using NSURLIsExcludedFromBackupKey).
  • When meeting iCloud backup conditions (network, battery, current time), data that can be backed up within the app sandbox will be synchronized with iCloud as a backup, which will only take effect upon reinstalling the app. However, document changes in iCloud Documents can be uploaded and synchronized to other devices almost in real-time.
  • When an app is deleted, the sandbox of that app will be cleared by the system, but files in the iCloud Documents folder will remain in iCloud and on the user’s devices.
  • Files in the iCloud Documents folder can be downloaded or released on demand (with the files still being saved in the cloud), while documents within the sandbox do not have this capability.
  • iCloud Documents provides version control and conflict resolution mechanisms, which help maintain file consistency when synchronizing between multiple devices.

Therefore, developers and users should decide on the usage strategy based on the characteristics of the iCloud Documents folder:

  • Since data in the iCloud Documents folder is synchronized, only documents that truly require instant backup and sharing should be placed in the iCloud Documents folder.
  • Considering that the data occupies space both locally and in the cloud, developers should provide the ability to free up space or remind users to release temporarily unnecessary resources through system applications.
  • Although the synchronization efficiency of iCloud Documents is acceptable, it is not suitable for storing scattered or incremental data. If necessary, developers can consider using other services provided by CloudKit.
  • Considering that users may have limited cloud storage capacity, developers should not assume that all data will be successfully uploaded to the cloud and synchronized to other devices by default.
  • To reduce the pressure on users’ cloud storage capacity, developers should provide the ability to transfer data to non-automatically synchronized directories.

What is a placeholder file

In cloud synchronization services, placeholder files play an important role. For example, if I create a file lesson1.pdf in the iCloud Documents directory on device A, device B, in most cases, will not automatically download the file when receiving the sync message (on macOS, the system will automatically download if optimize storage is turned off; on iOS, sometimes the system will automatically download if the file is small and the app is running). Instead, device B will create a corresponding placeholder file in the same location in the iCloud Documents directory. The app or user on device B can choose to download the complete file data from the cloud when needed.

Placeholder files provide a way to balance local storage limitations and immediate cloud file access. Through them, users can effectively manage their storage space while maintaining instant access to important files.

Taking device B and file file1.txt as an example, when device B receives the sync notification, it will create a file named .lesson1.pdf.icloud in the same location as lesson1.pdf on device A. This file will serve as a placeholder file for lesson1.pdf on device B.

Placeholder files store some information related to the original file (file name, file size, file type) in the form of Property List. After parsing, the approximate information is as follows:

Swift
[
  "NSURLNameKey": lesson1.pdf,
  "NSURLFileSizeKey": 206739,
  "NSURLFileResourceTypeKey": NSURLFileResourceTypeRegular
]

When File application or Finder detects that a file is a placeholder file, it will still display the file name and file size to the user as normal. However, it will use an icon to indicate that the file has not been downloaded to the local device yet. The user can click to download the full version from the cloud. Similarly, for fully downloaded files that are already on the local device, the user can click to remove the download item and the system will automatically create a new placeholder file.

Because of the existence of placeholder files, developers need to check the status of a file before performing certain operations on it.

Similarly, due to the special naming convention used for placeholder files, the best way to obtain a file list is still through the NSMetaDataQuery introduced in the previous article. Even if developers don’t have to consider file competition between multiple processes, using fileManager.contentsOfDirectory will include the placeholder identifier in the file names (for placeholder files), so developers still need to handle them differently.

How to Determine if a File is a Placeholder File

When working with iCloud Documents, properly identifying placeholder files is a crucial step. While we can check if the file name contains specific placeholder identifiers, this is not the most accurate or reliable method. A more scientific approach is to utilize the file list obtained through NSMetadataQuery and examine the metadata attributes of each file to determine if it is a placeholder file.

The advantage of this method is that it is based on the actual metadata state of the file, not just the file name. For this purpose, we have added an isPlaceholder property in the previously defined MetadataItemWrapper struct to store the placeholder status of each file.

Here is the corresponding Swift code implementation:

Swift
struct MetadataItemWrapper: Sendable {
    ....
    let isPlaceholder:Bool

    init(metadataItem: NSMetadataItem) {
        ....

        if let downloadingStatus = metadataItem.value(forAttribute: NSMetadataUbiquitousItemDownloadingStatusKey) as? String {
            if downloadingStatus == NSMetadataUbiquitousItemDownloadingStatusNotDownloaded {
                isPlaceholder = true
            } else if downloadingStatus == NSMetadataUbiquitousItemDownloadingStatusDownloaded || downloadingStatus == NSMetadataUbiquitousItemDownloadingStatusCurrent {
                isPlaceholder = false
            } else {
                isPlaceholder = false
            }
        } else {
            isPlaceholder = false
        }
    }
}

How to Download a File

Downloading a file refers to the process of retrieving the original file of a placeholder file from the cloud and replacing the placeholder file with it. By calling FileManager.default.startDownloadingUbiquitousItem(at:), you can trigger the downloading operation for a specific placeholder file. For safety reasons, it is recommended to use NSFileCoordinator to perform this operation.

Here is an example method for downloading a file, which utilizes the CloudDocumentsHandler created in the previous section to ensure the safety and coordination of the file download:

Swift
extension CloudDocumentsHandler {
    func download(url: URL) throws {
        var coordinationError: NSError?
        var downloadError: Error?

        coordinator.coordinate(writingItemAt: url, options: [.forDownloading], error: &coordinationError) { newURL in
            do {
                try FileManager.default.startDownloadingUbiquitousItem(at: newURL)
            } catch {
                downloadError = error
            }
        }

        // Check whether an error occurred during the download process
        if let error = downloadError {
            throw error
        }

        // Check whether an error occurred during the coordination process
        if let coordinationError = coordinationError {
            throw coordinationError
        }
    }
}

During the download process, the system does not save incomplete files in the current directory of the placeholder file. Only when the file is fully downloaded, the system will replace the placeholder file with the complete file.

Calling startDownloadingUbiquitousItem again for a file that has already been downloaded will have no effect.

Although the official documentation describes that this method can accept a directory URL as a parameter, it was found during testing that startDownloadingUbiquitousItem can only be used to download individual files.

How to obtain download progress, download status, and upload status

Download Progress: The download progress can be obtained from the file’s metadata NSMetadataUbiquitousItemPercentDownloadedKey. This value (Double) represents the percentage of the file that has been downloaded and can be used to track the download progress.

Download Status: By combining the placeholder status and download progress, the current download status can be determined. If the file is a placeholder file and the download progress is greater than 0 and less than 100, it can be considered that the file is currently being downloaded.

Upload Status: The upload progress can be obtained from the file’s metadata NSMetadataUbiquitousItemPercentUploadedKey. This value has only two states, 0 indicates that it has not been uploaded, and 100 indicates that it has been uploaded completely.

Swift
struct MetadataItemWrapper: Sendable {
    ....
    let isDownloading: Bool
    let downloadProgress: Double
    let uploaded: Bool

    init(metadataItem: NSMetadataItem) {
        ....
        // Get download progress
        downloadProgress = metadataItem.value(forAttribute: NSMetadataUbiquitousItemPercentDownloadedKey) as? Double ?? 0.0

        // If it is a placeholder file and the download progress is greater than 0 and less than 100, the file is considered to be downloading.
        isDownloading = isPlaceholder && downloadProgress > 0.0 && downloadProgress < 100.0
        // Whether the upload has been completed (only two states: 0 and 100)
        uploaded = (metadataItem.value(forAttribute: NSMetadataUbiquitousItemPercentUploadedKey) as? Double ?? 0.0) == 100
    }
}

With these attributes, we can accurately grasp the status of the file in order to better manage and monitor the synchronization process of the file and provide prompts to the user.

How to release the space occupied by downloaded files

When you need to release the space occupied by downloaded files and turn them back into placeholder mode, you can use the evictUbiquitousItem method. This method is very similar to triggering the download operation, but its purpose is to turn the downloaded file into a placeholder mode, thereby freeing up space. The evictUbiquitousItem method can be used for both files and folders. When performing this operation on a folder, iCloud will recursively remove copies of the sub-items in the folder.

It is important to note that do not use a coordinator (NSFileCoordinator) to perform this operation, as doing so may result in a deadlock. You can simply call the evictUbiquitousItem method to release the space occupied by downloaded files without the need for additional coordination.

Swift
extension CloudDocumentsHandler {
    func evict(url: URL) throws {
        do {
            try FileManager.default.evictUbiquitousItem(at: url)
        } catch {
            throw error
        }
    }
}

How to move files in the iCloud Documents directory without downloading them

You can move files in the iCloud Documents directory without worrying about their placeholder state by using the FileManager.default.moveItem(at:to:) method. Even if the file is a placeholder, as long as the destination address is also within the iCloud Documents directory, the file will remain in a placeholder state after the move.

Here is an example code that demonstrates how to move a file in the iCloud Documents directory:

Swift
extension CloudDocumentsHandler {
    func moveFile(at sourceURL: URL, to destinationURL: URL) throws {
        var coordinationError: NSError?
        var moveError: Error?

        coordinator.coordinate(writingItemAt: sourceURL, options: .forMoving, writingItemAt: destinationURL, options: .forReplacing, error: &coordinationError) { (newSourceURL, newDestinationURL) in
            do {
                try FileManager.default.moveItem(at: newSourceURL, to: newDestinationURL)
            } catch {
                moveError = error
            }
        }

        if let error = moveError {
            throw error
        }

        if let coordinationError = coordinationError {
            throw coordinationError
        }
    }
}

Please note that for specific operations such as moving files, it is important to ensure that the correct options are set in order to maintain the integrity of the files during the move process.

How to rename a file without downloading it

Simply use the code provided above for moving files and change the destination name. Even if it is a placeholder file, it will remain in a placeholder state after renaming.

How to unsync a file

You can unsync a file by moving it from the iCloud Documents directory to a different location that is not within the iCloud Documents directory. Even if the file is currently in a placeholder mode, the system will automatically start downloading the file before moving it to the new location. There may be some delay in this process, especially for larger files.

Debugging tips

When developing and debugging features that involve network synchronization, we often face a challenge: having a fast and stable network environment. While this is ideal, it doesn’t allow us to test edge cases of network synchronization, such as slow connections or unstable networks. Additionally, in high-speed network environments, certain critical transmission details and intermediate states may be skipped quickly, making it difficult to capture them.

To address this issue, developers can leverage a tool provided by Apple called the Network Link Conditioner. This tool allows us to simulate various network conditions, such as different internet speeds, delays, and packet loss rates, in order to create a network environment that closely resembles real-life scenarios.

In the process of writing this article, for example, I encountered difficulties in capturing the intermediate states of downloading iCloud Documents. This was due to the fast network speed, causing the download process to complete instantly. However, by using the Network Link Conditioner to artificially limit the network speed, I was able to simulate a slower download environment, enabling clear observation and recording of each stage of the download.

How to get Network Link Conditioner:

  • Download Additional Tools for Xcode from the Apple Developer website.

https://cdn.fatbobman.com/image-20231205102918859.png

https://cdn.fatbobman.com/image-20231205102856422.png

  • Open the downloaded .dmg file and locate Hardware/Network Link Conditioner.prefPane. Copy it to your local machine and double-click to install.

https://cdn.fatbobman.com/image-20231205103008115.png

https://cdn.fatbobman.com/image-20231205103020359.png

  • In the system settings, select or create a Profile, and enable this feature to achieve network control over the current development environment.

https://cdn.fatbobman.com/image-20231205103112525.png

Summary

Through the discussion in the two previous articles, we can see that integrating iCloud Documents into our project is not difficult as long as we carefully handle each step and debug carefully, despite the many details involved. Although this process requires us to invest time and effort, the added value and convenience it brings to the application are obvious.

The CloudKit service provided by Apple can be said to be a great blessing for developers. It allows developers to provide powerful and flexible network data synchronization functionality for applications at a very low cost. By using these features, the application will have stronger competitiveness and user appeal, and provide users with a better experience.

I'm really looking forward to hearing your thoughts! Please Leave Your Comments Below to share your views and insights.

Fatbobman(东坡肘子)

I'm passionate about life and sharing knowledge. My blog focuses on Swift, SwiftUI, Core Data, and Swift Data. Follow my social media for the latest updates.

You can support me in the following ways