Swift: عمليات ملفات وبيانات Swift: FileManager وCodable

Data persistence is a core capability of apps -- from saving user settings to caching network data. Mastering file I/O and encoding/decoding ensures your data survives app restarts.

1. What You'll Learn


2. An Indie Developer's Real Story

(1) Pain Point: All Data Gone After App Restart

Alice launched her first iOS to-do app. Users loved it, but there was one fatal flaw: close the app and reopen it, and all to-do items were gone. Users left one-star reviews on the App Store.

SWIFT
// Wrong approach: data only lives in memory
var todos: [TodoItem] = [
    TodoItem(title: "Buy coffee"),
    TodoItem(title: "Write weekly report"),
]

Alice's to-do data was stored in an array. When the app closed, memory was freed, and the data was lost.

(2) The Codable + FileManager Solution

SWIFT
struct TodoItem: Codable {
    let title: String
    var isDone: false
}
let encoder = JSONEncoder()
if let data = try? encoder.encode(todos) {
    let url = FileManager.default.documentsDirectory.appendingPathComponent("todos.json")
    try? data.write(to: url)
}

On the next launch, read from the file -- data is permanently saved.

(3) Benefits: User Data Never Lost

Dimension In-Memory Storage File Persistence
App restart Data lost Data restored
Storage capacity RAM limited Disk space
Data sharing Not supported Exportable/backup
Offline use Not dependent Fully offline
Restore speed Instant Millisecond-level

3. FileManager File Management

FileManager is Swift's file system interface, providing operations like creating, reading, moving, and deleting files.

(1) Common Directories

100%
graph TB
    A["FileManager.default"] --> B["documentsDirectory"]
    A --> C["cachesDirectory"]
    A --> D["temporaryDirectory"]
    B --> E["User data, backed up to iCloud"]
    C --> F["Cache files, system may purge"]
    D --> G["Temporary files, deleted at any time"]
Directory Purpose Backed Up? System Purges?
documentsDirectory User documents and data Yes No
cachesDirectory Cache files No Yes
temporaryDirectory Temporary files No Yes, anytime

▶ Example: Getting and Creating Directories

SWIFT
// ============================================
// Getting common directory paths and creating subdirectories
// ============================================
import Foundation
let fm = FileManager.default
// 1. Get the documents directory
let docs = fm.urls(for: .documentDirectory, in: .userDomainMask).first!
print("Documents directory: \(docs.path)")
// 2. Get the caches directory
let caches = fm.urls(for: .cachesDirectory, in: .userDomainMask).first!
print("Caches directory: \(caches.path)")
// 3. Create a subdirectory
let dataDir = docs.appendingPathComponent("MyAppData")
if !fm.fileExists(atPath: dataDir.path) {
    try? fm.createDirectory(at: dataDir, withIntermediateDirectories: true)
    print("Created directory: \(dataDir.path)")
}

Output:

TEXT 📖 للعرض فقط
Documents directory: /Users/alice/Library/Developer/.../Documents
Caches directory: /Users/alice/Library/Developer/.../Caches
Created directory: /Users/alice/Library/Developer/.../Documents/MyAppData

(2) Writing and Reading Files

FileManager provides basic methods for file operations, combined with Data's write and init(contentsOf:) for file I/O.

▶ Example: Writing and Reading a Text File

SWIFT
// ============================================
// Writing and reading a text file
// ============================================
import Foundation
let fm = FileManager.default
let docs = fm.urls(for: .documentDirectory, in: .userDomainMask).first!
// 1. Write text
let fileURL = docs.appendingPathComponent("note.txt")
let content = "Hello, Swift File I/O!"
try? content.write(to: fileURL, atomically: true, encoding: .utf8)
print("Write successful: \(fileURL.lastPathComponent)")
// 2. Read text
if let readContent = try? String(contentsOf: fileURL, encoding: .utf8) {
    print("Read content: \(readContent)")
}
// 3. Check if file exists
print("File exists: \(fm.fileExists(atPath: fileURL.path))")
// 4. Get file attributes
if let attrs = try? fm.attributesOfItem(atPath: fileURL.path) {
    let size = attrs[.size] as? Int ?? 0
    print("File size: \(size) bytes")
}

Output:

TEXT 📖 للعرض فقط
Write successful: note.txt
Read content: Hello, Swift File I/O!
File exists: true
File size: 21 bytes

4. Codable and JSON

Codable is a protocol combination (Encodable + Decodable) introduced in Swift 4.0 that makes JSON encoding and decoding effortless for custom types.

(1) The Codable Protocol

100%
graph LR
    A["Codable"] --> B["Encodable: encode to data"]
    A --> C["Decodable: decode from data"]
    B --> D["JSONEncoder().encode(obj)"]
    C --> E["JSONDecoder().decode(Type.self, from: data)"]
    D --> F["Data -> write to file"]
    E --> G["Data from file -> typed object"]
Protocol Method Purpose
Encodable func encode(to: Encoder) Encode an object to Data
Decodable init(from: Decoder) Decode from Data to an object
Codable Both combined Encodable and decodable

▶ Example: JSON Encoding and Decoding a Custom Object

SWIFT
// ============================================
// Using Codable to JSON encode/decode a Person
// ============================================
import Foundation
struct Person: Codable {
    let name: String
    let age: Int
    let email: String
}
// 1. Encode object to JSON
let alice = Person(name: "Alice", age: 28, email: "alice@example.com")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
if let jsonData = try? encoder.encode(alice),
   let jsonString = String(data: jsonData, encoding: .utf8) {
    print("Encoded JSON:")
    print(jsonString)
}
// 2. Decode from JSON to object
let json = """
{"name":"Bob","age":35,"email":"bob@example.com"}
""".data(using: .utf8)!
let decoder = JSONDecoder()
if let bob = try? decoder.decode(Person.self, from: json) {
    print("\nDecoded object: \(bob.name), age \(bob.age)")
}

Output:

TEXT 📖 للعرض فقط
Encoded JSON:
{
  "name" : "Alice",
  "age" : 28,
  "email" : "alice@example.com"
}

Decoded object: Bob, age 35

(2) Nested Objects and Custom Keys

Real-world JSON often has nested structures and different key names. Codable handles this with CodingKeys and nested types.

▶ Example: Complex JSON Parsing

SWIFT
// ============================================
// Nested object + custom JSON key name mapping
// ============================================
import Foundation
struct User: Codable {
    let id: Int
    let fullName: String
    let address: Address
    let tags: [String]
    // Custom key name mapping (JSON snake_case -> Swift camelCase)
    enum CodingKeys: String, CodingKey {
        case id
        case fullName = "full_name"
        case address
        case tags
    }
}
struct Address: Codable {
    let city: String
    let country: String
}
let json = """
{
    "id": 1001,
    "full_name": "Charlie Wang",
    "address": { "city": "Shanghai", "country": "China" },
    "tags": ["developer", "swift"]
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
if let user = try? decoder.decode(User.self, from: json) {
    print("User: \(user.fullName) (ID: \(user.id))")
    print("City: \(user.address.city)")
    print("Tags: \(user.tags.joined(separator: ", "))")
}

Output:

TEXT 📖 للعرض فقط
User: Charlie Wang (ID: 1001)
City: Shanghai
Tags: developer, swift

5. UserDefaults and Data Persistence

UserDefaults is suitable for storing small amounts of user preferences. It automatically integrates with iCloud sync and requires no manual file path management.

(1) Storing Data in UserDefaults

Storage Type Swift Type UserDefaults Method
Integer Int set(_:forKey:) + integer(forKey:)
Float Double set(_:forKey:) + double(forKey:)
Boolean Bool set(_:forKey:) + bool(forKey:)
String String set(_:forKey:) + string(forKey:)
Array [Any] set(_:forKey:) + array(forKey:)
Dictionary [String: Any] set(_:forKey:) + dictionary(forKey:)
Data Data set(_:forKey:) + data(forKey:)

▶ Example: Saving and Reading User Settings

SWIFT
// ============================================
// Saving user preferences with UserDefaults
// ============================================
import Foundation
let defaults = UserDefaults.standard
// 1. Save settings
defaults.set("Alice", forKey: "username")
defaults.set(true, forKey: "isLoggedIn")
defaults.set(3, forKey: "launchCount")
defaults.set(23.5, forKey: "lastTemperature")
// 2. Read settings
let username = defaults.string(forKey: "username") ?? "Guest"
let isLoggedIn = defaults.bool(forKey: "isLoggedIn")
let launchCount = defaults.integer(forKey: "launchCount")
let temperature = defaults.double(forKey: "lastTemperature")
print("User: \(username)")
print("Logged in: \(isLoggedIn)")
print("Launch count: \(launchCount)")
print("Last temperature: \(temperature)°C")
// 3. Remove a key
defaults.removeObject(forKey: "lastTemperature")
print("After removal: \(defaults.double(forKey: "lastTemperature"))")

Output:

TEXT 📖 للعرض فقط
User: Alice
Logged in: true
Launch count: 3
Last temperature: 23.5°C
After removal: 0.0

(2) Saving Complex Objects to UserDefaults with Codable

UserDefaults cannot directly store Codable objects, but it can store Data -- encode first, then store.

▶ Example: Storing a Custom Type in UserDefaults

SWIFT
// ============================================
// Codable + UserDefaults for storing complex objects
// ============================================
import Foundation
struct Settings: Codable {
    var theme: String = "light"
    var fontSize: Int = 14
    var enableNotifications: Bool = true
}
let defaults = UserDefaults.standard
let encoder = JSONEncoder()
let decoder = JSONDecoder()
// Save
var settings = Settings()
settings.theme = "dark"
settings.fontSize = 16
if let data = try? encoder.encode(settings) {
    defaults.set(data, forKey: "appSettings")
    print("Settings saved")
}
// Read
if let data = defaults.data(forKey: "appSettings"),
   let loaded = try? decoder.decode(Settings.self, from: data) {
    print("Theme: \(loaded.theme)")
    print("Font size: \(loaded.fontSize)")
    print("Notifications: \(loaded.enableNotifications)")
}

Output:

TEXT 📖 للعرض فقط
Settings saved
Theme: dark
Font size: 16
Notifications: true

6. Full Example: To-Do List Manager

SWIFT
// ============================================
// Full example: Persistent to-do list manager
// Features: CRUD + JSON file persistence
// ============================================
import Foundation
// 1. To-do item model
struct TodoItem: Codable {
    let id: UUID
    var title: String
    var isCompleted: Bool
    let createdAt: Date
    init(title: String) {
        self.id = UUID()
        self.title = title
        self.isCompleted = false
        self.createdAt = Date()
    }
}
// 2. To-do manager
class TodoManager {
    private var items: [TodoItem] = []
    private let fileURL: URL
    init() {
        let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        fileURL = docs.appendingPathComponent("todos.json")
        load()
    }
    func add(title: String) {
        items.append(TodoItem(title: title))
        save()
    }
    func toggle(id: UUID) {
        if let index = items.firstIndex(where: { $0.id == id }) {
            items[index].isCompleted.toggle()
            save()
        }
    }
    func showAll() {
        for item in items {
            let status = item.isCompleted ? "Done" : "Todo"
            print("[\(status)] \(item.title)")
        }
    }
    private func save() {
        let encoder = JSONEncoder()
        encoder.outputFormatting = .prettyPrinted
        if let data = try? encoder.encode(items) {
            try? data.write(to: fileURL)
        }
    }
    private func load() {
        if let data = try? Data(contentsOf: fileURL),
           let decoded = try? JSONDecoder().decode([TodoItem].self, from: data) {
            items = decoded
        }
    }
}
// 3. Usage example
let manager = TodoManager()
manager.add(title: "Learn Swift Codable")
manager.add(title: "Write file I/O tutorial")
manager.add(title: "Publish app update")
manager.showAll()
print("--- One completed ---")
// In practice, you'd toggle an item here

Output:

TEXT 📖 للعرض فقط
[Todo] Learn Swift Codable
[Todo] Write file I/O tutorial
[Todo] Publish app update
--- One completed ---

❓ FAQ

س How do I choose between UserDefaults and file storage?
ج UserDefaults is suitable for simple key-value pairs under 100KB (user preferences, tokens). Use file storage (JSON/database) for structured data or anything over 100KB. UserDefaults has poor performance and doesn't support partial updates.
س How do I control the output format of JSONEncoder?
ج Set encoder.outputFormatting to .prettyPrinted (human-readable), .sortedKeys (stable ordering), or .withoutEscapingSlashes (reduced escaping).
س Should I use String or URL for file paths?
ج Always use URL. URL supports path concatenation with appendingPathComponent, file attribute retrieval, and cross-platform compatibility. String paths are error-prone and lack these features.
س How should I use try? and try! in file operations?
ج File operations have a high probability of failure (disk full, permission denied, file not found). Always use try? or do-catch. try! crashes the app outright on failure.
س What information can FileManager's attributesOfItem provide?
ج File size, creation date, modification date, permissions, owner, whether it's a directory, and more. Access values with keys like attrs[.size], attrs[.creationDate], etc.
س What is the iOS sandbox mechanism?
ج Each app can only access its own sandbox directories (Documents, Caches, tmp) and cannot read/write other apps' file systems. URLs obtained via FileManager are within the sandbox scope.

📖 Summary


📝 Exercises

  1. Basic: Use FileManager to create a "MyNotes" directory under documentDirectory, write a greeting.txt file with the content "Hello, File I/O!", then read and print it.
  2. Intermediate: Create a Book struct (with title, author, year, isbn), save it to a file using JSONEncoder, then read it back with JSONDecoder and verify data consistency.
  3. Challenge: Implement a simple KV storage engine SimpleStorage based on a JSON file, supporting set(key: String, value: Codable) and get<T>(key: String) -> T? generic methods, with data persisted as a dictionary to file.
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%