Swift: Operações de Arquivo e Dados do Swift

A persistência de dados é uma capacidade central dos apps -- desde salvar configurações do usuário até armazenar em cache dados de rede. Dominar a E/S de arquivos e a codificação/decodificação garante que seus dados sobrevivam ao reinício do app.

1. O Que Você Vai Aprender


2. Uma História Real de uma Desenvolvedora Independente

(1) Problema: Todos os Dados Perdidos Após Reiniciar o App

Alice lançou seu primeiro app iOS de lista de tarefas. Os usuários adoraram, mas havia uma falha fatal: feche o app e reabra, e todos os itens da lista desapareciam. Os usuários deixaram avaliações de uma estrela na App Store.

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

Os dados da lista de Alice eram armazenados em um array. Quando o app fechava, a memória era liberada e os dados eram perdidos.

(2) A Solução com Codable + FileManager

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

Na próxima inicialização, leia do arquivo -- os dados são salvos permanentemente.

(3) Benefícios: Dados do Usuário Nunca Perdidos

Dimensão Armazenamento em Memória Persistência em Arquivo
Reinício do app Dados perdidos Dados restaurados
Capacidade de armazenamento RAM limitada Espaço em disco
Compartilhamento de dados Não suportado Exportável/backup
Uso offline Não dependente Totalmente offline
Velocidade de restauração Instantânea Nível de milissegundos

3. Gerenciamento de Arquivos com FileManager

FileManager é a interface do sistema de arquivos do Swift, fornecendo operações como criar, ler, mover e excluir arquivos.

(1) Diretórios Comuns

100%
graph TB
    A["FileManager.default"] --> B["documentsDirectory"]
    A --> C["cachesDirectory"]
    A --> D["temporaryDirectory"]
    B --> E["Dados do usuário, backup no iCloud"]
    C --> F["Arquivos de cache, sistema pode limpar"]
    D --> G["Arquivos temporários, excluídos a qualquer momento"]
Diretório Finalidade Backup? Sistema Limpa?
documentsDirectory Documentos e dados do usuário Sim Não
cachesDirectory Arquivos de cache Não Sim
temporaryDirectory Arquivos temporários Não Sim, a qualquer momento

▶ Exemplo: Obtendo e Criando Diretórios

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

Saída:

TEXT 📖 Somente leitura
Documents directory: /Users/alice/Library/Developer/.../Documents
Caches directory: /Users/alice/Library/Developer/.../Caches
Created directory: /Users/alice/Library/Developer/.../Documents/MyAppData

(2) Escrevendo e Lendo Arquivos

FileManager fornece métodos básicos para operações de arquivo, combinados com write e init(contentsOf:) de Data para E/S de arquivo.

▶ Exemplo: Escrevendo e Lendo um Arquivo de Texto

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

Saída:

TEXT 📖 Somente leitura
Write successful: note.txt
Read content: Hello, Swift File I/O!
File exists: true
File size: 21 bytes

4. Codable e JSON

Codable é uma combinação de protocolos (Encodable + Decodable) introduzida no Swift 4.0 que torna a codificação e decodificação JSON simples para tipos personalizados.

(1) O Protocolo Codable

100%
graph LR
    A["Codable"] --> B["Encodable: codificar para dados"]
    A --> C["Decodable: decodificar de dados"]
    B --> D["JSONEncoder().encode(obj)"]
    C --> E["JSONDecoder().decode(Tipo.self, from: dados)"]
    D --> F["Data -> escrever no arquivo"]
    E --> G["Data do arquivo -> objeto tipado"]
Protocolo Método Finalidade
Encodable func encode(to: Encoder) Codificar um objeto para Data
Decodable init(from: Decoder) Decodificar de Data para um objeto
Codable Ambos combinados Codificável e decodificável

▶ Exemplo: Codificação e Decodificação JSON de um Objeto Personalizado

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

Saída:

TEXT 📖 Somente leitura
JSON codificado:
{
  "nome" : "Alice",
  "idade" : 28,
  "email" : "alice@exemplo.com"
}

Objeto decodificado: Bob, idade 35

(2) Objetos Aninhados e Chaves Personalizadas

JSON do mundo real frequentemente tem estruturas aninhadas e nomes de chave diferentes. O Codable lida com isso usando CodingKeys e tipos aninhados.

▶ Exemplo: Análise de JSON Complexo

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: ", "))")
}

Saída:

TEXT 📖 Somente leitura
User: Charlie Wang (ID: 1001)
City: Shanghai
Tags: developer, swift

5. UserDefaults e Persistência de Dados

UserDefaults é adequado para armazenar pequenas quantidades de preferências do usuário. Ele se integra automaticamente com a sincronização do iCloud e não requer gerenciamento manual de caminho de arquivo.

(1) Armazenando Dados no UserDefaults

Tipo de Armazenamento Tipo Swift Método UserDefaults
Inteiro Int set(_:forKey:) + integer(forKey:)
Float Double set(_:forKey:) + double(forKey:)
Booleano Bool set(_:forKey:) + bool(forKey:)
String String set(_:forKey:) + string(forKey:)
Array [Any] set(_:forKey:) + array(forKey:)
Dicionário [String: Any] set(_:forKey:) + dictionary(forKey:)
Data Data set(_:forKey:) + data(forKey:)

▶ Exemplo: Salvando e Lendo Configurações do Usuário

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

Saída:

TEXT 📖 Somente leitura
User: Alice
Logged in: true
Launch count: 3
Last temperature: 23.5°C
After removal: 0.0

(2) Salvando Objetos Complexos no UserDefaults com Codable

UserDefaults não pode armazenar diretamente objetos Codable, mas pode armazenar Data -- codifique primeiro, depois armazene.

▶ Exemplo: Armazenando um Tipo Personalizado no 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)")
}

Saída:

TEXT 📖 Somente leitura
Settings saved
Theme: dark
Font size: 16
Notifications: true

6. Exemplo Completo: Gerenciador de Lista de Tarefas

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
> Saída:
> ```text
> [Todo] Learn Swift Codable
> [Todo] Write file I/O tutorial
> [Todo] Publish app update
> --- One completed ---
> ```
---
## ❓ Perguntas Frequentes
> **P: Como escolher entre UserDefaults e armazenamento em arquivo?** **R: UserDefaults é adequado para pares chave-valor simples abaixo de 100KB (preferências do usuário, tokens). Use armazenamento em arquivo (JSON/banco de dados) para dados estruturados ou acima de 100KB. UserDefaults tem desempenho ruim e não suporta atualizações parciais.**
> **P: Como controlar o formato de saída do JSONEncoder?** **R: Defina encoder.outputFormatting como .prettyPrinted (legível por humanos), .sortedKeys (ordenação estável) ou .withoutEscapingSlashes (escape reduzido).**
> **P: Devo usar String ou URL para caminhos de arquivo?** **R: Sempre use URL. URL suporta concatenação de caminho com `appendingPathComponent`, recuperação de atributos de arquivo e compatibilidade multiplataforma. Caminhos String são propensos a erros e não têm esses recursos.**
> **P: Como devo usar try? e try! em operações de arquivo?** **R: Operações de arquivo têm alta probabilidade de falha (disco cheio, permissão negada, arquivo não encontrado). Sempre use `try?` ou do-catch. `try!` trava o app completamente em caso de falha.**
> **P: Quais informações o attributesOfItem do FileManager pode fornecer?** **R: Tamanho do arquivo, data de criação, data de modificação, permissões, proprietário, se é um diretório e mais. Acesse os valores com chaves como `attrs[.size]`, `attrs[.creationDate]`, etc.**
> **P: O que é o mecanismo de sandbox do iOS?** **R: Cada app só pode acessar seus próprios diretórios de sandbox (Documents, Caches, tmp) e não pode ler/escrever nos sistemas de arquivos de outros apps. URLs obtidas via FileManager estão dentro do escopo da sandbox.**
---
## 📖 Resumo
- FileManager fornece a interface do sistema de arquivos; os diretórios comuns são documents, caches e temporary
- Codable (Encodable + Decodable) permite codificação/decodificação JSON para tipos personalizados
- JSONEncoder/JSONDecoder suportam saída formatada e mapeamento de nomes de chave personalizados
- UserDefaults é adequado para armazenamento de pequenas preferências, mas não para dados complexos ou grandes
- Objetos complexos podem ser codificados para Data primeiro, depois armazenados no UserDefaults
- Operações de arquivo devem tratar erros -- disco cheio, permissão negada, etc. são sempre possíveis
---
## 📝 Exercícios
1. **Básico:** Use FileManager para criar um diretório "MinhasNotas" em documentDirectory, escreva um arquivo saudacao.txt com o conteúdo "Olá, E/S de Arquivos!" e depois leia e imprima.
2. **Intermediário:** Crie uma struct Livro (com titulo, autor, ano, isbn), salve-a em um arquivo usando JSONEncoder, depois leia de volta com JSONDecoder e verifique a consistência dos dados.
3. **Desafio:** Implemente um mecanismo simples de armazenamento KV `ArmazenamentoSimples` baseado em um arquivo JSON, suportando métodos genéricos `definir(chave: String, valor: Codable)` e `obter<T>(chave: String) -> T?`, com dados persistidos como um dicionário no arquivo.
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%