All skills
dpearson2699 avatar

/ios-networking

@cf3fe87

Build, review, or improve networking code in iOS/macOS apps using URLSession with async/await, structured concurrency, and modern Swift patterns. Use when working with REST APIs, downloading files, uploading data, WebSocket connections, pagination, retry logic, request middleware, caching, background transfers, or network reachability monitoring. Also use when handling HTTP requests, API clients, network error handling, or data fetching in Swift apps.

Use this Skill: https://skilld.dev/gh/dpearson2699/swift-ios-skills/ios-networking

This session only. Nothing lands on disk.

referencesfile-storage-patterns.md

≈2.9k tokens on demand. Your agent reads this file only when SKILL.md points to it.

File Storage Patterns

Guidance on choosing the right directory, applying file protection, managing backup exclusions, and handling storage pressure on iOS.

Contents

Directory Selection Guide

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)

Accessing Standard Directories

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

Choosing the Right Directory

// 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")

FileProtectionType Levels

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

Setting File Protection

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
)

Checking Current Protection Level

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
    }
}

Handling Protected Data Availability

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
}

Backup Exclusion (isExcludedFromBackup)

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

Setting the Exclusion Flag

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)

Checking the Exclusion Flag

func isExcludedFromBackup(_ url: URL) throws -> Bool {
    let values = try url.resourceValues(forKeys: [.isExcludedFromBackupKey])
    return values.isExcludedFromBackup ?? false
}

When to Exclude from Backup

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

Common Pattern: Application Support with Exclusion

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

Storage Pressure Handling

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.

Checking Available Storage

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
}

Responding to Low Storage Notifications

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()
    }
}

Implementing Cleanup Strategies

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()

Source: SKILL.md on GitHub

No alerts17d4 checks · Risk SAFE
  • Gen Agent Trust Hub17d

    The skill provides secure patterns for iOS and macOS networking using URLSession and Network.framework. It correctly identifies security best practices, such as using Keychain for tokens and avoiding force-unwraps. A low-risk surface for indirect prompt injection exists as the skill facilitates fetching external data.

  • Socket17d

    No alerts

  • Snyk17d

    Risk: LOW · No issues

  • ZeroLeaks5mo

    1 finding · Score: 82/100

Signed by skilld at cf3fe87. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 days ago.

Steadyupdated 3 months ago
  • ios
  • urlsession
  • async-await
  • networking
  • swift
  • rest-api
  • http
  • error-handling
  • pagination

README badge

README badge for dpearson2699/swift-ios-skills/ios-networking

Handles REST APIs, file downloads/uploads, WebSockets, pagination, retry logic, and network reachability in iOS/macOS apps using URLSession with async/await and structured concurrency. Covers API client architecture, request middleware, token refresh, error handling, and App Transport Security configuration for Swift 6.3+.

Generated from the current SKILL.md.

Does this skill work with iOS versions before iOS 15?
No. The skill targets iOS 15+ because URLSession's native async/await overloads were introduced in iOS 15. It explicitly does not cover completion-handler variants used in older code.
Does this skill cover third-party networking libraries like Alamofire?
No. The skill focuses exclusively on URLSession with async/await and structured concurrency. It does not require or discuss third-party dependencies.
What Swift version does this skill target?
Swift 6.3. All examples use modern Swift patterns including structured concurrency and Sendable conformance.
Does this cover WebSocket connections?
The skill description mentions WebSocket connections as a use case, but the provided content does not include WebSocket implementation details — only URLSession patterns for HTTP requests, downloads, uploads, and streaming.
Should I use URLSession.shared or create a custom session?
Use URLSession.shared only for simple, one-off requests. Production code should create a configured URLSession with custom timeout intervals, cache policies, and headers.

Generated from the current SKILL.md. These answers refresh after source changes.