当前位置:首页 > 问答 > 正文

iOS里多线程访问数据库怎么弄才不会卡顿或者数据错乱呢?

在iOS开发中,让App运行流畅不卡顿是我们的核心目标之一,当你的App需要处理大量数据,比如一个聊天应用或者新闻阅读器,把数据存到本地数据库(比如SQLite,或者苹果自家的Core Data)是个好主意,但问题来了,如果你在用户滑动列表、快速操作的同时,又在后台拼命读写数据库,就很容易出现界面卡顿,甚至因为多个线程同时改数据导致程序崩溃或者数据错乱。

要解决这个问题,核心思想其实很简单:给数据库的访问建立一个“单行通道”,让所有想读写数据库的“人”(也就是各个线程)都乖乖地排队,一次只允许一个操作进行。 想象一下银行只有一个办事窗口,不管来多少人,都得按顺序来,这样就不会乱。

在iOS世界里,实现这个“单行通道”有几种主流且有效的方法,我们一种一种来看。

第一种方法,也是苹果最推荐的方法:使用Core Data配合正确的并发模型。

Core Data是苹果自家的数据管理框架,它天生就为多线程环境做了考虑,关键在于,你不能随意把一个Core Data的对象(比如你从数据库里取出来的一个“用户”对象)在不同的线程之间扔来扔去,这是绝对禁止的,会立刻导致崩溃。

正确的做法是,为不同的线程创建不同的“上下文”,你可以把“上下文”理解成是一个临时的工作区,苹果建议的模式是:

  1. 创建一个主队列上下文:这个上下文专门和主线程(也就是UI线程)绑定,它只用来从数据库里读取数据,并用来更新界面,因为它在主线程上,所以当你取到数据后,UI的刷新是即时且安全的。
  2. 创建一个或多个私有队列上下文:当你要进行耗时操作,比如从网络下载一大堆数据然后存入数据库时,你应该在一个后台线程里,创建一个新的、私有的上下文,你在这个“后台工作区”里进行所有的增删改查操作。
  3. 通过通知进行同步:当后台的私有上下文保存成功时,Core Data会发送一个通知,你的主队列上下文可以订阅这个通知,一旦收到通知,主队列上下文就会自动合并这些更改,这样,你UI上显示的数据就能自动、安全地更新了。

这种方法的好处是,UI线程永远不会被耗时的数据库操作阻塞,界面始终保持流畅,Core Data在底层帮我们处理了复杂的合并冲突问题,非常省心,根据苹果官方文档《Core Data Programming Guide》中“Concurrency with Core Data”章节的描述,这是确保线程安全的标准做法。

第二种方法,如果你使用的是原生的SQLite数据库。

很多开发者出于性能或跨平台考虑,会选择直接使用SQLite,SQLite本身是线程安全的,但需要你以正确的方式打开数据库连接。

  1. 使用串行队列(Serial Queue):这是最直接、最不容易出错的方法,你可以创建一个专门的串行队列(比如叫databaseQueue),然后把你所有读写数据库的代码块都放到这个队列里去执行,因为队列是串行的,所以这些代码块会一个接一个地执行,彻底避免了同时访问。

    • 写操作:比如插入新数据、删除数据,都包装在databaseQueue.async { ... }里。
    • 读操作:同样也包装在队列里,但要注意,如果读操作是为了更新UI,你可能需要在这个代码块里取到数据后,再跳回主线程去刷新界面,这可以通过在数据库操作完成后,在里面再写一个DispatchQueue.main.async { ... }来实现。

    这种方法本质上就是手动实现了我们开头说的“银行单窗口”模型,效果非常好。

  2. 谨慎使用并发队列:SQLite也支持用并发模式打开,允许多个线程同时读,但同一时间只能有一个线程写,虽然这听起来能提升读的性能,但你需要非常小心地处理读写之间的锁,实现起来更复杂,容易出错,对于大多数App来说,串行队列的性能已经足够好,所以优先推荐串行队列方案。

总结一下关键点:

  • 核心原则:避免多线程直接共享数据库连接或数据对象,通过“排队”机制(队列或特定的上下文)来序列化所有访问操作。
  • 对于Core Data:采用主上下文更新UI,私有上下文处理后台任务的模式,并利用通知机制进行数据同步。
  • 对于SQLite:创建一个专用的串行调度队列,将所有数据库操作封装在该队列的块中执行。
  • UI更新:无论哪种方法,记住一条黄金法则——任何与用户界面相关的更新(比如刷新表格、更新标签文字)都必须在主线程上执行,在后台线程完成数据操作后,要主动切换回主线程。

只要遵循这些简单的规则,你就能轻松地让数据库操作在后台安静地进行,同时保证用户界面的丝滑流畅和数据的安全可靠。

iOS里多线程访问数据库怎么弄才不会卡顿或者数据错乱呢?