应用技术
数据持久化

数据持久化

一、iOS 数据持久化面试题(带答案详解)


1. 什么是数据持久化?为什么需要它?

答案:

数据持久化(Data Persistence)指的是将内存中的数据保存到设备的本地存储中,使得应用在退出或重启后仍能恢复这些数据。

作用:

  • 保存用户偏好、登录状态等信息;
  • 缓存网络数据,减少请求;
  • 存储业务数据,如聊天记录、离线数据等。

2. iOS 中常见的数据持久化方式有哪些?

答案:

  1. UserDefaults(NSUserDefaults)

    用于存储小规模的轻量级数据(如布尔值、字符串、数字、设置状态等)。

  2. Property List(Plist 文件)

    存储结构化数据(如字典、数组),适合简单配置或缓存。

  3. 归档(NSKeyedArchiver / Codable)

    将对象序列化后存储到文件,可保存自定义对象。

  4. 文件系统(FileManager)

    手动操作文件进行读写,适合文本、图片、音频等数据。

  5. SQLite 数据库

    关系型数据库,适合中大型结构化数据。

  6. Core Data

    苹果官方提供的 ORM(对象关系映射)框架,封装了 SQLite,适合复杂对象管理。


3. UserDefaults 的原理是什么?适合存什么?

答案:

UserDefaults 本质是基于 plist 文件的键值存储。它会将数据缓存到内存中,并在合适的时机自动写入磁盘。

适用场景:

  • 登录状态;
  • 设置开关;
  • 上次打开的页面索引。

注意:

不要存储大体量数据或敏感信息(如密码、token)。


4. 如何安全地存储用户敏感数据?

答案:

使用 Keychain(钥匙串)来存储。

Keychain 由系统管理,数据经过加密,支持多 App 共享(同一个 App Group)。

示例代码(Swift):

let account = "user@example.com"
let password = "123456"
let data = password.data(using: .utf8)!

let query: [String: Any] = [
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrAccount as String: account,
    kSecValueData as String: data
]
SecItemAdd(query as CFDictionary, nil)

5. Codable 与 NSCoding 的区别?

特性CodableNSCoding
类型Swift 原生协议Objective-C 协议
底层原理JSON / PropertyList 编解码二进制归档
使用简便性自动生成,简单易用需手动实现 encode / init(coder:)
性能更快稍慢
兼容性Swift-onlyObjC / Swift 均可

建议:

新项目优先使用 Codable


6. Codable 如何实现对象序列化?

答案:

通过 Encodable 和 Decodable 协议,结合 JSONEncoder / JSONDecoder 实现。

示例:

struct User: Codable {
    var name: String
    var age: Int
}

// 编码
let user = User(name: "Alex", age: 25)
if let data = try? JSONEncoder().encode(user) {
    UserDefaults.standard.set(data, forKey: "user")
}

// 解码
if let data = UserDefaults.standard.data(forKey: "user"),
   let user = try? JSONDecoder().decode(User.self, from: data) {
    print(user.name)
}

7. Core Data 的主要组成部分有哪些?

答案:

Core Data 主要包括以下组件:

  1. NSManagedObjectModel:定义数据模型结构;
  2. NSPersistentStoreCoordinator:协调数据存储;
  3. NSManagedObjectContext:对象上下文,负责数据的增删改查;
  4. NSPersistentContainer:iOS 10+ 封装的简化管理类。

8. Core Data 的优缺点是什么?

优点:

  • 对象化管理数据;
  • 自动关系维护(如一对多);
  • 支持懒加载、内存优化;
  • 有强大的查询与版本迁移支持。

缺点:

  • 学习曲线较高;
  • 不适合高频快速写入;
  • 调试复杂。

9. Core Data 与 SQLite 的区别?

对比项Core DataSQLite
封装层级高层封装(对象管理)底层数据库
适用场景面向对象数据结构关系型数据结构
迁移自动或轻量级迁移手动修改表结构
性能中等高性能(写入频繁)
学习难度较高较低(SQL语句)

10. 如何在 iOS 中使用 SQLite?

答案:

  1. 使用系统框架 SQLite3;
  2. 或使用第三方库,如 FMDBGRDB

示例:

import SQLite3

var db: OpaquePointer?
if sqlite3_open("/path/to/db.sqlite3", &db) == SQLITE_OK {
    print("Database opened successfully")
}

FMDB 封装了 SQLite 的 C 接口,使其更易于在 Swift/ObjC 中使用。


11. 如何管理不同版本的 Core Data 模型?

答案:

  • 在 .xcdatamodeld 文件中创建多个版本;
  • 通过 “Set Current” 设置当前版本;
  • 启用 轻量级迁移(Lightweight Migration)
let options = [NSMigratePersistentStoresAutomaticallyOption: true,
               NSInferMappingModelAutomaticallyOption: true]

12. 文件存储常见路径有哪些?

路径说明
Documents/用户数据,支持 iTunes 备份
Library/Caches/缓存数据,系统可能清理
tmp/临时文件,随时可能被清除
AppGroup/多 App 共享存储区域

使用 FileManager 获取路径:

let path = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!

13. 数据持久化设计时要考虑哪些因素?

答案:

  • 数据体量与访问频率;
  • 是否涉及敏感信息;
  • 是否需支持结构化查询;
  • 是否需跨 App/设备同步;
  • 性能与安全的权衡。

示例:

  • 小数据 → UserDefaults;
  • 大对象 → FileManager;
  • 复杂关系 → Core Data;
  • 高性能存取 → SQLite;
  • 安全信息 → Keychain。

二、总结

iOS 数据持久化方式从轻到重可概括为:

UserDefaults → Plist → Codable → File → SQLite → Core Data → Keychain

实际开发中常常是混合使用

例如登录状态存 Keychain、用户设置存 UserDefaults、离线数据用 Core Data 或 SQLite。


是否希望我帮你接着输出这一部分的 “实战场景题 + 答案”(例如:缓存设计、Core Data 多线程操作、数据库迁移策略等)

三、iOS 数据持久化实战场景题(附答案详解)


1. 场景题:如何实现一个本地缓存系统(如新闻、视频列表)?

答案思路:

缓存系统通常结合 内存缓存 + 磁盘缓存 设计,以平衡性能与持久化。

设计方案:

  1. 短期缓存(内存)

    使用 NSCache 存放热点数据,提高访问速度。

    • 自动清理;
    • 支持线程安全;
    • 不会被持久化。
  2. 长期缓存(磁盘)

    使用 FileManager 或 SQLite/Core Data 存储数据。

    • 将 JSON 数据序列化为文件;
    • 定期清理过期文件;
    • 结合时间戳和版本号。

示例伪代码:

class CacheManager {
    static let shared = CacheManager()
    private let memoryCache = NSCache<NSString, NSData>()

    func save(data: Data, for key: String) {
        memoryCache.setObject(data as NSData, forKey: key as NSString)
        let path = cachePath(for: key)
        try? data.write(to: URL(fileURLWithPath: path))
    }

    func load(for key: String) -> Data? {
        if let data = memoryCache.object(forKey: key as NSString) {
            return data as Data
        }
        if let data = try? Data(contentsOf: URL(fileURLWithPath: cachePath(for: key))) {
            memoryCache.setObject(data as NSData, forKey: key as NSString)
            return data
        }
        return nil
    }

    private func cachePath(for key: String) -> String {
        let dir = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first!
        return "\(dir)/\(key.hash)"
    }
}

要点:

  • 优先读取内存;
  • 定期清理磁盘;
  • 可通过 MD5 生成唯一 key;
  • 对数据结构复杂的内容(如视频元信息)可结合 Codable 或 SQLite 存储索引。

2. 场景题:Core Data 如何处理多线程?

答案:

Core Data 的 NSManagedObjectContext 不是线程安全的,每个线程都应拥有自己的上下文。

推荐做法:

  1. 主线程 context(UI 操作)

    用于显示、查询。

  2. 后台 context(耗时操作)

    用于写入、批量导入等。

示例:

let container = NSPersistentContainer(name: "Model")
container.loadPersistentStores { _, _ in }

let backgroundContext = container.newBackgroundContext()
backgroundContext.perform {
    let user = User(context: backgroundContext)
    user.name = "Alex"
    try? backgroundContext.save()
}

关键点:

  • 使用 .perform {} 或 .performAndWait {} 保证线程安全;
  • 写入完成后使用通知或父子上下文同步到主线程;
  • 避免在不同线程间直接传递 NSManagedObject。

3. 场景题:如何设计一个通用的数据持久化层?

答案:

通过 Repository 模式DataManager 封装底层逻辑。

设计思路:

  1. 定义统一接口:
protocol DataStorage {
    func save<T: Codable>(_ object: T, for key: String)
    func load<T: Codable>(for key: String) -> T?
}
  1. 实现不同存储方式:
class UserDefaultsStorage: DataStorage {
    func save<T>(_ object: T, for key: String) where T : Codable {
        if let data = try? JSONEncoder().encode(object) {
            UserDefaults.standard.set(data, forKey: key)
        }
    }

    func load<T>(for key: String) -> T? where T : Codable {
        guard let data = UserDefaults.standard.data(forKey: key) else { return nil }
        return try? JSONDecoder().decode(T.self, from: data)
    }
}
  1. 统一调用:
let storage: DataStorage = UserDefaultsStorage()
storage.save(User(name: "Alice", age: 18), for: "user")
let user: User? = storage.load(for: "user")

优势:

  • 易于替换(可换成 Core Data、SQLite 实现);
  • 有利于单元测试;
  • 模块化、解耦。

4. 场景题:如何实现应用数据的“版本迁移”?

答案:

不同版本的应用可能需要调整数据结构,可通过数据迁移策略实现。

迁移方式:

  1. UserDefaults / 文件迁移:
    • 读取旧字段;
    • 转换为新结构;
    • 存回新字段;
    • 删除旧字段。
  2. Core Data 迁移:
    • 使用 轻量级迁移(轻微变更字段、属性);
    • 若结构变化大,需使用 Mapping Model

示例(轻量迁移):

let options = [
    NSMigratePersistentStoresAutomaticallyOption: true,
    NSInferMappingModelAutomaticallyOption: true
]
try coordinator.addPersistentStore(ofType: NSSQLiteStoreType,
                                   configurationName: nil,
                                   at: storeURL,
                                   options: options)

5. 场景题:如何处理数据库文件体积过大?

答案:

  1. 定期清理无效数据;
  2. 避免重复存储相同资源;
  3. 使用 WAL 模式 提高性能;
  4. 对图片/视频等资源,仅存储路径,不直接写入数据库;
  5. 使用 VACUUM 优化 SQLite 空间。
sqlite3_exec(db, "VACUUM;", nil, nil, nil)

6. 场景题:App 需要在多个设备同步用户数据,怎么办?

答案:

可采用 CloudKitiCloud Key-Value Store

方式适用场景特点
iCloud Key-Value Store小量配置同步类似 UserDefaults
CloudKit复杂对象/表结构类似服务器数据库

示例:

let store = NSUbiquitousKeyValueStore.default
store.set("dark", forKey: "theme")
store.synchronize()

注意:

  • CloudKit 同步延迟较高;
  • 大量数据应考虑服务端同步方案(如 Firebase / 自建后端)。

7. 场景题:如果你要实现一个聊天记录存储系统,会怎么设计?

答案思路:

  • 存储结构:
    • Message(消息表)
    • Conversation(会话表)
    • User(用户表)
  • 实现方式:
    • 使用 SQLite / Core Data;
    • 按会话 ID 建索引;
    • 消息按时间排序;
    • 支持分页查询与搜索。

性能优化:

  • 延迟加载消息(分页查询);
  • 写入使用事务;
  • 定期压缩数据库。

8. 场景题:UserDefaults 存太多数据有什么风险?

答案:

  • 存储在 ~/Library/Preferences 下;
  • iOS 在启动时会加载到内存;
  • 过大可能造成 App 启动变慢
  • 存储频繁写入可能影响性能。

建议:

  • 控制在几百 KB 内;
  • 不存大型 JSON 或图片;
  • 定期清理废弃 key。

9. 场景题:如何安全删除用户的本地数据?

答案:

  1. 删除 Documents、Caches、tmp 中所有文件;
  2. 清除 UserDefaults;
  3. 清空 Keychain;
  4. 清理 Core Data / SQLite。

示例:

UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)

注意:

Keychain 不会自动随 App 卸载清除,若有重置账号需求需手动调用 SecItemDelete。


10. 场景题:如何判断数据是否需要更新(本地缓存 vs 服务器)?

答案:

可通过以下策略:

  1. 时间戳策略:超过一定时间自动刷新;
  2. ETag 策略:对比服务器返回的 ETag;
  3. 版本号策略:服务器返回最新版本标识;
  4. Hash 校验:通过内容哈希判断变化。

示例:

if lastUpdate + 3600 < Date().timeIntervalSince1970 {
    refreshData()
}

四、总结与面试应答技巧

面试官常考逻辑链:

UserDefaults → Keychain → FileManager → Codable → SQLite → Core Data → CloudKit

答题时要体现:

  • 你理解每种方案的适用场景与边界
  • 知道其线程、安全、性能特性;
  • 能说出实际项目中的使用例子

是否要我接着输出一份 “Core Data & SQLite 对比实战题 + 性能优化技巧总结版”,作为这一章的拓展内容?