Agent skills for iOS, iPadOS, Swift, SwiftUI, and modern Apple framework development.
71
89%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Advisory
Suggest reviewing before use
Guidance on choosing the right directory, applying file protection, managing backup exclusions, and handling storage pressure on iOS.
iOS provides four primary directories for app data. Choose based on whether the data is user-generated, re-creatable, or temporary.
| Directory | Backed Up | Purged by System | Use For |
|---|---|---|---|
Documents/ | Yes | No | User-generated content (documents, exports, user files) |
Library/Application Support/ | Yes | No | App-generated supporting files (databases, config, caches that should survive updates) |
Library/Caches/ | No | Yes (low storage) | Re-creatable data (downloaded images, API responses, computed data) |
tmp/ | No | Yes (anytime) | Truly temporary files (in-progress uploads, scratch files) |
Use FileManager.default.urls(for:in:) to get the correct path. Never
hardcode paths.
import Foundation
// Documents/ — user-generated content, backed up by iCloud/iTunes
let documentsURL = FileManager.default.urls(
for: .documentDirectory, in: .userDomainMask
).first!
// Library/Application Support/ — app-generated supporting data, backed up
let appSupportURL = FileManager.default.urls(
for: .applicationSupportDirectory, in: .userDomainMask
).first!
// Create if it doesn't exist (not auto-created)
try FileManager.default.createDirectory(
at: appSupportURL, withIntermediateDirectories: true
)
// Library/Caches/ — re-creatable data, not backed up, may be purged
let cachesURL = FileManager.default.urls(
for: .cachesDirectory, in: .userDomainMask
).first!
// tmp/ — temporary files, purged by system periodically
let tmpURL = FileManager.default.temporaryDirectory// User's exported PDF — Documents/
let exportURL = documentsURL.appendingPathComponent("Report.pdf")
try pdfData.write(to: exportURL)
// App's SQLite database — Library/Application Support/
let dbURL = appSupportURL.appendingPathComponent("AppData.sqlite")
// Downloaded thumbnail cache — Library/Caches/
let thumbURL = cachesURL.appendingPathComponent("thumbnails/\(imageID).jpg")
// In-progress upload — tmp/
let uploadURL = tmpURL.appendingPathComponent(UUID().uuidString + ".tmp")iOS encrypts files at rest using Data Protection. The protection level determines when the file is accessible relative to the device lock state.
Docs: FileProtectionType, Encrypting Your App's Files
| Level | Constant | When Accessible | Use For |
|---|---|---|---|
| Complete | .complete | Only when device is unlocked | Sensitive user data (health records, financial data) |
| Complete Unless Open | .completeUnlessOpen | Can finish if opened before lock | Active downloads, recordings in progress |
| Until First Auth | .completeUntilFirstUserAuthentication | After first unlock (default) | Most app data; background-accessible content |
| None | .none | Always, even before first unlock | Non-sensitive system-required data |
import Foundation
// Option 1: Set protection when writing data
try sensitiveData.write(to: fileURL, options: .completeFileProtection)
// Option 2: Set protection via FileManager attributes
try FileManager.default.setAttributes(
[.protectionKey: FileProtectionType.complete],
ofItemAtPath: fileURL.path
)
// Option 3: Set protection on a directory (applies to new files within)
try FileManager.default.setAttributes(
[.protectionKey: FileProtectionType.complete],
ofItemAtPath: secureDirectoryURL.path
)let attributes = try FileManager.default.attributesOfItem(atPath: fileURL.path)
if let protection = attributes[.protectionKey] as? FileProtectionType {
switch protection {
case .complete:
print("File is fully protected")
case .completeUnlessOpen:
print("Protected unless already open")
case .completeUntilFirstUserAuthentication:
print("Protected until first unlock (default)")
case .none:
print("No encryption")
default:
break
}
}Files with .complete protection are inaccessible when the device is locked.
Check availability before accessing:
import UIKit
// Check if protected data is currently available
if UIApplication.shared.isProtectedDataAvailable {
// Safe to read .complete files
let data = try Data(contentsOf: protectedFileURL)
} else {
// Wait for device unlock
}
// Observe availability changes
NotificationCenter.default.addObserver(
forName: UIApplication.protectedDataDidBecomeAvailableNotification,
object: nil,
queue: .main
) { _ in
// Protected files are now accessible
}
NotificationCenter.default.addObserver(
forName: UIApplication.protectedDataWillBecomeUnavailableNotification,
object: nil,
queue: .main
) { _ in
// Close file handles to .complete files
}Exclude large re-downloadable content from iCloud/iTunes backup to avoid bloating the user's backup. Apple may reject apps that back up excessive re-creatable data.
Docs: URLResourceValues
import Foundation
// Exclude a file or directory from backup
func excludeFromBackup(_ url: URL) throws {
var resourceValues = URLResourceValues()
resourceValues.isExcludedFromBackup = true
var mutableURL = url
try mutableURL.setResourceValues(resourceValues)
}
// Usage
let largeCache = cachesURL.appendingPathComponent("video-cache")
try excludeFromBackup(largeCache)func isExcludedFromBackup(_ url: URL) throws -> Bool {
let values = try url.resourceValues(forKeys: [.isExcludedFromBackupKey])
return values.isExcludedFromBackup ?? false
}| Exclude | Keep in Backup |
|---|---|
| Downloaded media (images, videos, audio) | User-created documents |
| API response caches | User preferences and settings |
| Generated thumbnails or previews | App databases with user data |
| Offline map tiles | In-app purchase receipts |
| Pre-computed search indexes | User-generated content |
Store re-downloadable data in Application Support but exclude from backup:
let offlineDataURL = appSupportURL.appendingPathComponent("OfflineData")
try FileManager.default.createDirectory(
at: offlineDataURL, withIntermediateDirectories: true
)
try excludeFromBackup(offlineDataURL)
// Files in this directory persist across app updates but don't bloat backup
try downloadedData.write(to: offlineDataURL.appendingPathComponent("map-tiles.db"))When the device runs low on storage, iOS may purge files in Library/Caches/
and tmp/. Apps should proactively manage storage and respond to low-space
conditions.
import Foundation
func availableDiskSpace() throws -> Int64 {
let values = try URL(fileURLWithPath: NSHomeDirectory())
.resourceValues(forKeys: [.volumeAvailableCapacityForImportantUsageKey])
return values.volumeAvailableCapacityForImportantUsage ?? 0
}
// Use .volumeAvailableCapacityForImportantUsageKey for important operations
// Use .volumeAvailableCapacityForOpportunisticUsageKey for optional operations
// The opportunistic value is always <= the important value
func hasSpaceForDownload(bytes: Int64) throws -> Bool {
let available = try availableDiskSpace()
return available > bytes
}import UIKit
// iOS posts this when storage is critically low (UIKit apps)
NotificationCenter.default.addObserver(
forName: UIApplication.didReceiveMemoryWarningNotification,
object: nil,
queue: .main
) { _ in
// Clear in-memory caches; consider trimming disk caches too
clearImageCache()
}
// Check storage proactively at app launch or before large operations
func checkStorageAndCleanup() throws {
let availableBytes = try availableDiskSpace()
let threshold: Int64 = 100 * 1024 * 1024 // 100 MB
if availableBytes < threshold {
try performCleanup()
}
}import Foundation
struct StorageCleaner {
let cachesURL: URL
let maxCacheAge: TimeInterval // e.g., 7 days
let maxCacheSize: Int64 // e.g., 500 MB
/// Remove files older than maxCacheAge
func removeExpiredFiles() throws {
let contents = try FileManager.default.contentsOfDirectory(
at: cachesURL,
includingPropertiesForKeys: [.contentModificationDateKey, .fileSizeKey],
options: .skipsHiddenFiles
)
let cutoff = Date.now.addingTimeInterval(-maxCacheAge)
for fileURL in contents {
let values = try fileURL.resourceValues(
forKeys: [.contentModificationDateKey]
)
if let modified = values.contentModificationDate, modified < cutoff {
try FileManager.default.removeItem(at: fileURL)
}
}
}
/// Trim cache to maxCacheSize using LRU eviction
func trimToSize() throws {
let contents = try FileManager.default.contentsOfDirectory(
at: cachesURL,
includingPropertiesForKeys: [.contentModificationDateKey, .fileSizeKey],
options: .skipsHiddenFiles
)
// Sort oldest first
let sorted = try contents.sorted { a, b in
let aDate = try a.resourceValues(forKeys: [.contentModificationDateKey])
.contentModificationDate ?? .distantPast
let bDate = try b.resourceValues(forKeys: [.contentModificationDateKey])
.contentModificationDate ?? .distantPast
return aDate < bDate
}
// Calculate total size
var totalSize: Int64 = 0
for fileURL in sorted {
let values = try fileURL.resourceValues(forKeys: [.fileSizeKey])
totalSize += Int64(values.fileSize ?? 0)
}
// Delete oldest files until under budget
for fileURL in sorted {
guard totalSize > maxCacheSize else { break }
let values = try fileURL.resourceValues(forKeys: [.fileSizeKey])
let fileSize = Int64(values.fileSize ?? 0)
try FileManager.default.removeItem(at: fileURL)
totalSize -= fileSize
}
}
/// Full cleanup: expired files first, then trim to size
func performCleanup() throws {
try removeExpiredFiles()
try trimToSize()
}
}
// Usage
let cleaner = StorageCleaner(
cachesURL: FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!,
maxCacheAge: 7 * 24 * 60 * 60, // 7 days
maxCacheSize: 500 * 1024 * 1024 // 500 MB
)
try cleaner.performCleanup()skills
accessorysetupkit
references
activitykit
references
adattributionkit
references
alarmkit
references
app-clips
app-intents
references
app-store-optimization
app-store-review
apple-on-device-ai
appmigrationkit
references
audioaccessorykit
references
authentication
references
avkit
references
background-processing
references
browserenginekit
references
callkit
references
carplay
references
cloudkit
references
contacts-framework
references
core-bluetooth
references
core-data
core-motion
references
core-nfc
references
coreml
references
cryptokit
references
cryptotokenkit
references
debugging-instruments
device-integrity
references
dockkit
references
energykit
references
eventkit
references
financekit
references
focus-engine
gamekit
references
healthkit
references
homekit
references
ios-accessibility
ios-localization
ios-networking
ios-simulator
references
mapkit
metrickit
references
musickit
references
natural-language
references
paperkit
references
passkit
references
pdfkit
references
pencilkit
references
permissionkit
references
photokit
push-notifications
realitykit
references
relevancekit
references
scenekit
references
sensorkit
references
speech-recognition
spritekit
references
storekit
swift-api-design-guidelines
swift-architecture
swift-charts
references
swift-codable
swift-concurrency
swift-formatstyle
swift-language
swift-security
references
swift-testing
swiftdata
swiftlint
swiftui-animation
swiftui-gestures
references
swiftui-layout-components
swiftui-liquid-glass
references
swiftui-patterns
swiftui-performance
swiftui-uikit-interop
swiftui-webkit
tabletopkit
references
tipkit
references
vision-framework
weatherkit
references
widgetkit
references