Add scan flow MVP and local Axiom skill workspace
This snapshot establishes the camera-to-result recognition flow and related tests while checking in the project skill/docs assets required for the configured local tooling.
This commit is contained in:
613
.claude/skills/axiom-storage-management-ref/SKILL.md
Normal file
613
.claude/skills/axiom-storage-management-ref/SKILL.md
Normal file
@@ -0,0 +1,613 @@
|
||||
---
|
||||
name: axiom-storage-management-ref
|
||||
description: Use when asking about 'purge files', 'storage pressure', 'disk space iOS', 'isExcludedFromBackup', 'URL resource values', 'volumeAvailableCapacity', 'low storage', 'file purging priority', 'cache management' - comprehensive reference for iOS storage management and URL resource value APIs
|
||||
license: MIT
|
||||
compatibility: iOS 5.0+, iPadOS 5.0+, macOS 10.7+
|
||||
metadata:
|
||||
version: "1.0.0"
|
||||
last-updated: "2025-12-12"
|
||||
---
|
||||
|
||||
# iOS Storage Management Reference
|
||||
|
||||
**Purpose**: Comprehensive reference for storage pressure, purging policies, disk space, and URL resource values
|
||||
**Availability**: iOS 5.0+ (basic), iOS 11.0+ (modern capacity APIs)
|
||||
**Context**: Answer to "Does iOS provide any way to mark files as 'purge as last resort'?"
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when you need to:
|
||||
- Understand iOS file purging behavior
|
||||
- Check available disk space correctly
|
||||
- Set purge priorities for cached files
|
||||
- Exclude files from backup
|
||||
- Monitor storage pressure
|
||||
- Mark files as purgeable
|
||||
- Understand volume capacity APIs
|
||||
- Handle "low storage" scenarios
|
||||
|
||||
## The Core Question
|
||||
|
||||
> **"Does iOS provide any way to mark files as 'purge as last resort'?"**
|
||||
|
||||
**Answer**: Not directly, but iOS provides two approaches:
|
||||
|
||||
1. **Location-based purging** (implicit priority):
|
||||
- `tmp/` → Purged aggressively (anytime)
|
||||
- `Library/Caches/` → Purged under storage pressure
|
||||
- `Documents/`, `Application Support/` → Never purged
|
||||
|
||||
2. **Capacity checking** (explicit strategy):
|
||||
- `volumeAvailableCapacityForImportantUsage` — For must-save data
|
||||
- `volumeAvailableCapacityForOpportunisticUsage` — For nice-to-have data
|
||||
- Check before saving, choose location based on available space
|
||||
|
||||
---
|
||||
|
||||
## URL Resource Values for Storage
|
||||
|
||||
### Complete Reference Table
|
||||
|
||||
| Resource Key | Type | Purpose | Availability |
|
||||
|--------------|------|---------|--------------|
|
||||
| `volumeAvailableCapacityKey` | Int64 | Total available space | iOS 5.0+ |
|
||||
| `volumeAvailableCapacityForImportantUsageKey` | Int64 | Space for essential files | iOS 11.0+ |
|
||||
| `volumeAvailableCapacityForOpportunisticUsageKey` | Int64 | Space for optional files | iOS 11.0+ |
|
||||
| `volumeTotalCapacityKey` | Int64 | Total volume capacity | iOS 5.0+ |
|
||||
| `isExcludedFromBackupKey` | Bool | Exclude from iCloud/iTunes backup | iOS 5.1+ |
|
||||
| `isPurgeableKey` | Bool | System can delete under pressure | iOS 9.0+ |
|
||||
| `fileAllocatedSizeKey` | Int64 | Actual disk space used | iOS 5.0+ |
|
||||
| `totalFileAllocatedSizeKey` | Int64 | Total allocated (including metadata) | iOS 5.0+ |
|
||||
|
||||
### Checking Available Space (Modern Approach)
|
||||
|
||||
```swift
|
||||
// ✅ CORRECT: Check appropriate capacity before saving
|
||||
func checkSpaceBeforeSaving(fileSize: Int64, isEssential: Bool) -> Bool {
|
||||
let homeURL = FileManager.default.homeDirectoryForCurrentUser
|
||||
|
||||
do {
|
||||
let values = try homeURL.resourceValues(forKeys: [
|
||||
.volumeAvailableCapacityForImportantUsageKey,
|
||||
.volumeAvailableCapacityForOpportunisticUsageKey
|
||||
])
|
||||
|
||||
if isEssential {
|
||||
// For must-save data (user-created content, critical app data)
|
||||
let importantCapacity = values.volumeAvailableCapacityForImportantUsage ?? 0
|
||||
return fileSize < importantCapacity
|
||||
} else {
|
||||
// For nice-to-have data (caches, thumbnails)
|
||||
let opportunisticCapacity = values.volumeAvailableCapacityForOpportunisticUsage ?? 0
|
||||
return fileSize < opportunisticCapacity
|
||||
}
|
||||
} catch {
|
||||
print("Error checking capacity: \(error)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
if checkSpaceBeforeSaving(fileSize: imageData.count, isEssential: true) {
|
||||
try imageData.write(to: documentsURL.appendingPathComponent("photo.jpg"))
|
||||
} else {
|
||||
showLowStorageAlert()
|
||||
}
|
||||
```
|
||||
|
||||
### Important vs Opportunistic Capacity
|
||||
|
||||
**volumeAvailableCapacityForImportantUsage**:
|
||||
- Space reserved for **essential** operations
|
||||
- Use for: User-created content, must-save data
|
||||
- System reserves this space more aggressively
|
||||
- Higher threshold
|
||||
|
||||
**volumeAvailableCapacityForOpportunisticUsage**:
|
||||
- Space available for **optional** operations
|
||||
- Use for: Caches, thumbnails, pre-fetching
|
||||
- Lower threshold (system may already be under pressure)
|
||||
- Indicates "go ahead if you want, but system is getting full"
|
||||
|
||||
```swift
|
||||
// ✅ CORRECT: Different thresholds for different data types
|
||||
func shouldDownloadThumbnail(size: Int64) -> Bool {
|
||||
let capacity = try? FileManager.default.homeDirectoryForCurrentUser
|
||||
.resourceValues(forKeys: [.volumeAvailableCapacityForOpportunisticUsageKey])
|
||||
.volumeAvailableCapacityForOpportunisticUsage ?? 0
|
||||
|
||||
// Only download optional content if there's plenty of space
|
||||
return size < capacity
|
||||
}
|
||||
|
||||
func canSaveUserDocument(size: Int64) -> Bool {
|
||||
let capacity = try? FileManager.default.homeDirectoryForCurrentUser
|
||||
.resourceValues(forKeys: [.volumeAvailableCapacityForImportantUsageKey])
|
||||
.volumeAvailableCapacityForImportantUsage ?? 0
|
||||
|
||||
// User documents are essential
|
||||
return size < capacity
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Backup Exclusion
|
||||
|
||||
### isExcludedFromBackup
|
||||
|
||||
Files in `Caches/` are automatically excluded from backup, but you should **explicitly mark** re-downloadable files in other directories.
|
||||
|
||||
```swift
|
||||
// ✅ CORRECT: Exclude large re-downloadable files from backup
|
||||
func markExcludedFromBackup(url: URL) throws {
|
||||
var resourceValues = URLResourceValues()
|
||||
resourceValues.isExcludedFromBackup = true
|
||||
try url.setResourceValues(resourceValues)
|
||||
}
|
||||
|
||||
// Example: Downloaded podcast episodes
|
||||
func downloadPodcast(url: URL) throws {
|
||||
let appSupportURL = FileManager.default.urls(
|
||||
for: .applicationSupportDirectory,
|
||||
in: .userDomainMask
|
||||
)[0]
|
||||
|
||||
let podcastURL = appSupportURL
|
||||
.appendingPathComponent("Podcasts")
|
||||
.appendingPathComponent(url.lastPathComponent)
|
||||
|
||||
// Download file
|
||||
let data = try Data(contentsOf: url)
|
||||
try data.write(to: podcastURL)
|
||||
|
||||
// Mark as excluded from backup (can re-download)
|
||||
try markExcludedFromBackup(url: podcastURL)
|
||||
}
|
||||
```
|
||||
|
||||
**When to exclude from backup**:
|
||||
- ✅ Downloaded content that can be re-fetched
|
||||
- ✅ Generated thumbnails
|
||||
- ✅ Cached API responses
|
||||
- ✅ Large media files from server
|
||||
- ❌ User-created content (always back up)
|
||||
- ❌ App data that can't be recreated
|
||||
|
||||
### Checking Backup Status
|
||||
|
||||
```swift
|
||||
// ✅ Check if file is excluded from backup
|
||||
func isExcludedFromBackup(url: URL) -> Bool {
|
||||
let values = try? url.resourceValues(forKeys: [.isExcludedFromBackupKey])
|
||||
return values?.isExcludedFromBackup ?? false
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Purgeable Files
|
||||
|
||||
### isPurgeable
|
||||
|
||||
Mark files as candidates for automatic purging by the system.
|
||||
|
||||
```swift
|
||||
// ✅ CORRECT: Mark cache files as purgeable
|
||||
func markAsPurgeable(url: URL) throws {
|
||||
var resourceValues = URLResourceValues()
|
||||
resourceValues.isPurgeable = true
|
||||
try url.setResourceValues(resourceValues)
|
||||
}
|
||||
|
||||
// Example: Thumbnail cache
|
||||
func cacheThumbnail(image: UIImage, for url: URL) throws {
|
||||
let cacheURL = FileManager.default.urls(
|
||||
for: .cachesDirectory,
|
||||
in: .userDomainMask
|
||||
)[0]
|
||||
|
||||
let thumbnailURL = cacheURL.appendingPathComponent(url.lastPathComponent)
|
||||
|
||||
// Save thumbnail
|
||||
try image.pngData()?.write(to: thumbnailURL)
|
||||
|
||||
// Mark as purgeable
|
||||
try markAsPurgeable(url: thumbnailURL)
|
||||
|
||||
// Also exclude from backup
|
||||
var resourceValues = URLResourceValues()
|
||||
resourceValues.isExcludedFromBackup = true
|
||||
try thumbnailURL.setResourceValues(resourceValues)
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: Files in `Caches/` are already purgeable by location. Setting `isPurgeable` is advisory for files in other locations.
|
||||
|
||||
---
|
||||
|
||||
## Implicit Purge Priority (Location-Based)
|
||||
|
||||
iOS purges files based on **location**, not explicit priority flags.
|
||||
|
||||
### Purge Priority Hierarchy
|
||||
|
||||
```
|
||||
PURGED FIRST (Aggressive):
|
||||
└── tmp/
|
||||
- Purged: Anytime (even while app running)
|
||||
- Lifetime: Hours to days
|
||||
- Use for: Truly temporary intermediates
|
||||
|
||||
PURGED SECOND (Storage Pressure):
|
||||
└── Library/Caches/
|
||||
- Purged: When system needs space
|
||||
- Lifetime: Weeks to months (if space available)
|
||||
- Use for: Re-downloadable, regenerable content
|
||||
|
||||
NEVER PURGED (Permanent):
|
||||
├── Documents/
|
||||
│ - Backed up: ✅ Yes
|
||||
│ - Purged: ❌ Never (unless app deleted)
|
||||
│ - Use for: User-created content
|
||||
│
|
||||
└── Library/Application Support/
|
||||
- Backed up: ✅ Yes
|
||||
- Purged: ❌ Never (unless app deleted)
|
||||
- Use for: Essential app data
|
||||
```
|
||||
|
||||
### Implementation Strategy
|
||||
|
||||
```swift
|
||||
// ✅ CORRECT: Choose location based on purge priority needs
|
||||
func saveFile(data: Data, priority: FilePriority) throws {
|
||||
let url: URL
|
||||
|
||||
switch priority {
|
||||
case .essential:
|
||||
// Never purged - for user-created or critical app data
|
||||
url = FileManager.default.urls(
|
||||
for: .documentDirectory,
|
||||
in: .userDomainMask
|
||||
)[0].appendingPathComponent("important.dat")
|
||||
|
||||
case .cacheable:
|
||||
// Purged under storage pressure - for re-downloadable content
|
||||
url = FileManager.default.urls(
|
||||
for: .cachesDirectory,
|
||||
in: .userDomainMask
|
||||
)[0].appendingPathComponent("cache.dat")
|
||||
|
||||
case .temporary:
|
||||
// Purged aggressively - for temp files
|
||||
url = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent("temp.dat")
|
||||
}
|
||||
|
||||
try data.write(to: url)
|
||||
|
||||
// For cacheable files, mark excluded from backup
|
||||
if priority == .cacheable {
|
||||
var resourceValues = URLResourceValues()
|
||||
resourceValues.isExcludedFromBackup = true
|
||||
try url.setResourceValues(resourceValues)
|
||||
}
|
||||
}
|
||||
|
||||
enum FilePriority {
|
||||
case essential // Never purge
|
||||
case cacheable // Purge under pressure
|
||||
case temporary // Purge aggressively
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Storage Pressure Detection
|
||||
|
||||
### Responding to Low Storage
|
||||
|
||||
```swift
|
||||
// ✅ CORRECT: Monitor for low storage and clean up proactively
|
||||
class StorageMonitor {
|
||||
func checkStorageAndCleanup() {
|
||||
let homeURL = FileManager.default.homeDirectoryForCurrentUser
|
||||
|
||||
guard let values = try? homeURL.resourceValues(forKeys: [
|
||||
.volumeAvailableCapacityForOpportunisticUsageKey,
|
||||
.volumeTotalCapacityKey
|
||||
]) else { return }
|
||||
|
||||
let availableSpace = values.volumeAvailableCapacityForOpportunisticUsage ?? 0
|
||||
let totalSpace = values.volumeTotalCapacity ?? 1
|
||||
|
||||
// Calculate percentage
|
||||
let percentAvailable = Double(availableSpace) / Double(totalSpace)
|
||||
|
||||
if percentAvailable < 0.10 { // Less than 10% free
|
||||
print("⚠️ Low storage detected, cleaning up...")
|
||||
cleanupCaches()
|
||||
}
|
||||
}
|
||||
|
||||
func cleanupCaches() {
|
||||
let cacheURL = FileManager.default.urls(
|
||||
for: .cachesDirectory,
|
||||
in: .userDomainMask
|
||||
)[0]
|
||||
|
||||
// Delete old cache files
|
||||
let fileManager = FileManager.default
|
||||
guard let files = try? fileManager.contentsOfDirectory(
|
||||
at: cacheURL,
|
||||
includingPropertiesForKeys: [.contentModificationDateKey]
|
||||
) else { return }
|
||||
|
||||
// Sort by modification date
|
||||
let sortedFiles = files.sorted { url1, url2 in
|
||||
let date1 = (try? url1.resourceValues(forKeys: [.contentModificationDateKey]))?.contentModificationDate
|
||||
let date2 = (try? url2.resourceValues(forKeys: [.contentModificationDateKey]))?.contentModificationDate
|
||||
return (date1 ?? .distantPast) < (date2 ?? .distantPast)
|
||||
}
|
||||
|
||||
// Delete oldest files first
|
||||
for fileURL in sortedFiles.prefix(100) {
|
||||
try? fileManager.removeItem(at: fileURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Background Cleanup Task
|
||||
|
||||
```swift
|
||||
// ✅ CORRECT: Register background task to clean up storage
|
||||
import BackgroundTasks
|
||||
|
||||
func registerBackgroundCleanup() {
|
||||
BGTaskScheduler.shared.register(
|
||||
forTaskWithIdentifier: "com.example.app.cleanup",
|
||||
using: nil
|
||||
) { task in
|
||||
self.handleStorageCleanup(task: task as! BGProcessingTask)
|
||||
}
|
||||
}
|
||||
|
||||
func handleStorageCleanup(task: BGProcessingTask) {
|
||||
task.expirationHandler = {
|
||||
task.setTaskCompleted(success: false)
|
||||
}
|
||||
|
||||
// Clean up old caches
|
||||
cleanupOldFiles()
|
||||
|
||||
task.setTaskCompleted(success: true)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Size Calculation
|
||||
|
||||
### Getting Accurate File Sizes
|
||||
|
||||
```swift
|
||||
// ✅ CORRECT: Get actual disk usage (includes filesystem overhead)
|
||||
func getFileSize(url: URL) -> Int64? {
|
||||
let values = try? url.resourceValues(forKeys: [
|
||||
.fileAllocatedSizeKey,
|
||||
.totalFileAllocatedSizeKey
|
||||
])
|
||||
|
||||
// Use totalFileAllocatedSize for accurate disk usage
|
||||
return values?.totalFileAllocatedSize.map { Int64($0) }
|
||||
}
|
||||
|
||||
// ✅ Calculate directory size
|
||||
func getDirectorySize(url: URL) -> Int64 {
|
||||
guard let enumerator = FileManager.default.enumerator(
|
||||
at: url,
|
||||
includingPropertiesForKeys: [.totalFileAllocatedSizeKey]
|
||||
) else { return 0 }
|
||||
|
||||
var totalSize: Int64 = 0
|
||||
|
||||
for case let fileURL as URL in enumerator {
|
||||
if let size = getFileSize(url: fileURL) {
|
||||
totalSize += size
|
||||
}
|
||||
}
|
||||
|
||||
return totalSize
|
||||
}
|
||||
|
||||
// Usage
|
||||
let cacheSize = getDirectorySize(url: cachesDirectory)
|
||||
print("Cache using \(cacheSize / 1_000_000) MB")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Pattern 1: Smart Download Based on Available Space
|
||||
|
||||
```swift
|
||||
// ✅ CORRECT: Only download optional content if space available
|
||||
func downloadOptionalContent(url: URL, size: Int64) async throws {
|
||||
// Check opportunistic capacity
|
||||
let homeURL = FileManager.default.homeDirectoryForCurrentUser
|
||||
let values = try homeURL.resourceValues(forKeys: [
|
||||
.volumeAvailableCapacityForOpportunisticUsageKey
|
||||
])
|
||||
|
||||
guard let available = values.volumeAvailableCapacityForOpportunisticUsage,
|
||||
size < available else {
|
||||
print("Skipping download - low storage")
|
||||
return
|
||||
}
|
||||
|
||||
// Proceed with download
|
||||
let data = try await URLSession.shared.data(from: url).0
|
||||
try data.write(to: cachesDirectory.appendingPathComponent(url.lastPathComponent))
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 2: Progressive Cache Cleanup
|
||||
|
||||
```swift
|
||||
// ✅ CORRECT: Clean up caches when approaching storage limits
|
||||
class CacheManager {
|
||||
func addToCache(data: Data, key: String) throws {
|
||||
let cacheURL = getCacheURL(for: key)
|
||||
|
||||
// Check if we should clean up first
|
||||
if shouldCleanupCache(addingSize: Int64(data.count)) {
|
||||
cleanupOldestFiles(targetSize: 100 * 1_000_000) // 100 MB
|
||||
}
|
||||
|
||||
try data.write(to: cacheURL)
|
||||
}
|
||||
|
||||
func shouldCleanupCache(addingSize: Int64) -> Bool {
|
||||
let homeURL = FileManager.default.homeDirectoryForCurrentUser
|
||||
guard let values = try? homeURL.resourceValues(forKeys: [
|
||||
.volumeAvailableCapacityForOpportunisticUsageKey
|
||||
]) else { return false }
|
||||
|
||||
let available = values.volumeAvailableCapacityForOpportunisticUsage ?? 0
|
||||
|
||||
// Clean up if less than 200 MB free
|
||||
return available < 200 * 1_000_000
|
||||
}
|
||||
|
||||
func cleanupOldestFiles(targetSize: Int64) {
|
||||
// Delete oldest cache files until under target
|
||||
// (implementation similar to earlier example)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 3: Exclude Downloaded Media from Backup
|
||||
|
||||
```swift
|
||||
// ✅ CORRECT: Downloaded podcast/video management
|
||||
class MediaDownloader {
|
||||
func downloadMedia(url: URL) async throws {
|
||||
let data = try await URLSession.shared.data(from: url).0
|
||||
|
||||
// Store in Application Support (not Caches, so it persists)
|
||||
let mediaURL = applicationSupportDirectory
|
||||
.appendingPathComponent("Downloads")
|
||||
.appendingPathComponent(url.lastPathComponent)
|
||||
|
||||
try data.write(to: mediaURL)
|
||||
|
||||
// But exclude from backup (can re-download)
|
||||
var resourceValues = URLResourceValues()
|
||||
resourceValues.isExcludedFromBackup = true
|
||||
try mediaURL.setResourceValues(resourceValues)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Debugging Storage Issues
|
||||
|
||||
### Audit Backup Size
|
||||
|
||||
```swift
|
||||
// ✅ Check what's being backed up
|
||||
func auditBackupSize() {
|
||||
let documentsURL = FileManager.default.urls(
|
||||
for: .documentDirectory,
|
||||
in: .userDomainMask
|
||||
)[0]
|
||||
|
||||
let size = getDirectorySize(url: documentsURL)
|
||||
print("Documents (backed up): \(size / 1_000_000) MB")
|
||||
|
||||
// Check for large files that should be excluded
|
||||
if size > 100 * 1_000_000 { // > 100 MB
|
||||
print("⚠️ Large backup size - check for re-downloadable files")
|
||||
findLargeFiles(in: documentsURL)
|
||||
}
|
||||
}
|
||||
|
||||
func findLargeFiles(in directory: URL) {
|
||||
guard let enumerator = FileManager.default.enumerator(
|
||||
at: directory,
|
||||
includingPropertiesForKeys: [.totalFileAllocatedSizeKey]
|
||||
) else { return }
|
||||
|
||||
for case let fileURL as URL in enumerator {
|
||||
if let size = getFileSize(url: fileURL),
|
||||
size > 10 * 1_000_000 { // > 10 MB
|
||||
print("Large file: \(fileURL.lastPathComponent) (\(size / 1_000_000) MB)")
|
||||
|
||||
// Check if excluded from backup
|
||||
if !isExcludedFromBackup(url: fileURL) {
|
||||
print("⚠️ Should this be excluded from backup?")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Task | API | Code |
|
||||
|------|-----|------|
|
||||
| Check space for essential file | `volumeAvailableCapacityForImportantUsageKey` | `values.volumeAvailableCapacityForImportantUsage` |
|
||||
| Check space for cache | `volumeAvailableCapacityForOpportunisticUsageKey` | `values.volumeAvailableCapacityForOpportunisticUsage` |
|
||||
| Exclude from backup | `isExcludedFromBackupKey` | `resourceValues.isExcludedFromBackup = true` |
|
||||
| Mark purgeable | `isPurgeableKey` | `resourceValues.isPurgeable = true` |
|
||||
| Get file size | `totalFileAllocatedSizeKey` | `values.totalFileAllocatedSize` |
|
||||
| Purge priority | Location-based | Use `tmp/` or `Caches/` directory |
|
||||
|
||||
---
|
||||
|
||||
## File Protection Quick Reference
|
||||
|
||||
Set encryption level per file. See `axiom-file-protection-ref` for full guide.
|
||||
|
||||
| Level | When Accessible | Use For |
|
||||
|-------|----------------|---------|
|
||||
| `.complete` | Only while unlocked | Passwords, tokens, health data |
|
||||
| `.completeUnlessOpen` | After first unlock if already open | Active downloads, media recording |
|
||||
| `.completeUntilFirstUserAuthentication` | After first unlock (default) | Most app data |
|
||||
| `.none` | Always, even before unlock | Background fetch data, push payloads |
|
||||
|
||||
```swift
|
||||
// Set protection on file
|
||||
try data.write(to: url, options: .completeFileProtection)
|
||||
|
||||
// Set protection on directory
|
||||
try FileManager.default.createDirectory(
|
||||
at: url,
|
||||
withIntermediateDirectories: true,
|
||||
attributes: [.protectionKey: FileProtectionType.complete]
|
||||
)
|
||||
|
||||
// Check current protection
|
||||
let values = try url.resourceValues(forKeys: [.fileProtectionKey])
|
||||
print("Protection: \(values.fileProtection ?? .none)")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Skills
|
||||
|
||||
- `axiom-storage` — Decide where to store files
|
||||
- `axiom-file-protection-ref` — File encryption and security
|
||||
- `axiom-storage-diag` — Debug storage-related issues
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2025-12-12
|
||||
**Skill Type**: Reference
|
||||
**Minimum iOS**: 5.0 (basic), 11.0 (modern capacity APIs)
|
||||
Reference in New Issue
Block a user