iOS中的数据库

数据持久化是移动端开发的核心能力之一。iOS提供了从轻量级KV存储到完整关系数据库的多层次方案。本文将从底层存储原理出发,系统性地梳理iOS中各类数据库与持久化技术的设计思想、实现机制和适用场景。 一、iOS数据持久化方案全景 iOS中常见的数据持久化方案,按数据复杂度和使用场景可分为以下几层: graph TD subgraph KV["轻量级KV存储"] A["UserDefaultsplist序列化"] B["Keychain加密存储"] C["MMKVmmap + protobuf"] end subgraph FILE["文件存储"] D["plist / JSON"] E["NSCoding / Codable归档"] end subgraph SQL["关系数据库"] F["SQLiteC语言API"] G["FMDBOC封装"] H["WCDBORM + 加密"] end subgraph ORM["ORM框架"] I["Core DataApple官方"] J["Realm零拷贝"] K["SwiftDataSwift原生"] end KV --> |"数据结构更复杂"| FILE FILE --> |"需要结构化查询"| SQL SQL --> |"需要对象映射"| ORM 方案 数据模型 查询能力 线程安全 加密 适用场景 UserDefaults KV (plist类型) Key查找 线程安全 无 用户偏好、简单配置 Keychain KV (Data) Key查找 线程安全 系统加密 密码、Token、证书 MMKV KV (protobuf) Key查找 线程安全 可选AES 高频读写的KV数据 plist/JSON文件 字典/数组 无 不安全 无 静态配置、缓存 NSCoding/Codable 对象图 无 不安全 无 对象序列化 SQLite 关系表 SQL 需手动处理 可选(SQLCipher) 结构化数据、复杂查询 FMDB 关系表 SQL FMDatabaseQueue 可选 SQLite的OC封装 WCDB 关系表+ORM SQL+ORM 自动管理 内置 高性能结构化存储 Core Data 对象图 NSPredicate NSManagedObjectContext 无 复杂对象关系 Realm 对象 类型安全查询 对象冻结 可选AES 高性能对象存储 SwiftData Swift对象 #Predicate宏 ModelContext 无 Swift原生数据持久化 二、轻量级KV存储 2.1 UserDefaults UserDefaults是iOS最常用的轻量级存储方案,底层将数据以plist格式序列化到磁盘。 ...

May 2, 2026

iOS中的内存管理

本文将系统介绍iOS中的内存管理机制,从程序内存布局、引用计数原理、ARC机制到系统级内存管理,帮助你建立完整的iOS内存管理知识体系。 一、程序内存布局 内存区域划分 在iOS应用程序中,内存按照用途和管理方式分为以下几个区域: 高地址 ┌─────────────────────────────────────┐ │ 栈区 (Stack) │ ↓ 向低地址增长 │ 局部变量、函数参数、返回地址 │ ├─────────────────────────────────────┤ │ ↕ │ │ 动态分配区 │ │ ↕ │ ├─────────────────────────────────────┤ │ 堆区 (Heap) │ ↑ 向高地址增长 │ 动态分配的对象实例 │ ├─────────────────────────────────────┤ │ 全局/静态区 (BSS) │ │ 未初始化的全局变量和静态变量 │ ├─────────────────────────────────────┤ │ 数据区 (Data) │ │ 已初始化的全局变量和静态变量 │ ├─────────────────────────────────────┤ │ 常量区 (Rodata) │ │ 字符串常量等 │ ├─────────────────────────────────────┤ │ 代码区 (Text) │ │ 编译后的代码 │ └─────────────────────────────────────┘ 低地址 各区域的特点: ...

May 2, 2026

iOS 定时器的注意事项

iOS 开发中常用的定时器有 NSTimer、CADisplayLink 和 GCD Timer。它们各有特点,但在使用时都有一些需要注意的问题。 常见定时器类型 1. NSTimer 最常用的定时器,基于 RunLoop 实现: // 自动添加到当前 RunLoop 的 DefaultMode NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerFired) userInfo:nil repeats:YES]; // 手动创建,需要手动添加到 RunLoop NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerFired) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; 2. CADisplayLink 与屏幕刷新率同步的定时器,适合做动画: CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)]; [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 3. GCD Timer 不依赖 RunLoop,精度更高: dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue()); dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0); dispatch_source_set_event_handler(timer, ^{ NSLog(@"GCD Timer fired"); }); dispatch_resume(timer); 注意事项一:循环引用问题 问题描述 NSTimer 和 CADisplayLink 在使用 target-action 模式时,会强引用 target。如果 target 又持有定时器,就会形成循环引用: ...

May 2, 2026

Swift二进制兼容性

Swift二进制兼容性是指Swift编译后的二进制代码能够跨版本、跨模块正确链接和运行的能力。它由三个核心机制组成:ABI稳定(Swift 5.0)、模块稳定性(Swift 5.1)和Library Evolution。这三者共同使得Swift二进制框架的分发成为可能。 graph TD A[Swift二进制兼容性] --> B[ABI稳定Swift 5.0] A --> C[模块稳定性Swift 5.1] A --> D[Library Evolution] B --> B1[统一运行时固定调用约定/内存布局] C --> C1[.swiftinterface跨编译器版本导入模块] D --> D1[运行时布局查询库可独立于客户端更新] B1 --> E[二进制框架分发] C1 --> E D1 --> E ABI稳定 什么是ABI ABI(Application Binary Interface,应用程序二进制接口)定义了二进制层面的接口规范,包括: 数据类型的大小和对齐方式:如Int在64位系统上是8字节 函数调用约定:参数如何传递、返回值如何获取 名称修饰(Name Mangling)规则:函数和类型在二进制中的命名方式 内存布局:对象在内存中的结构 异常处理机制:错误如何在二进制层面传播 运行时元数据格式:类型信息的存储方式 ABI vs API 特性 API(源码接口) ABI(二进制接口) 层面 源代码层面 编译后的二进制层面 兼容性检查 编译时 链接时/运行时 关注点 函数签名、类型定义 内存布局、调用约定 变化影响 需要重新编译 需要重新链接或替换二进制 一个简单的例子: // API层面:函数签名 func calculate(value: Int) -> Int // ABI层面关注的是: // - Int在内存中占多少字节 // - value参数通过哪个寄存器传递 // - 返回值通过哪个寄存器返回 // - 函数在二进制中的符号名是什么 ABI不稳定时期的问题 在Swift 5.0之前,Swift的ABI是不稳定的。这意味着: ...

May 2, 2026

Swift中import详解

源码版本说明:本文涉及的源码基于 Swift 编译器 开发版本(swift main分支,commit: e0db5152d4d4,2026-01-19)。不同版本的实现细节可能略有差异,但核心机制保持一致。 在前一篇文章Objective-C中import详解中,我们深入探讨了 Objective-C 中的 import 机制,包括 #include、#import、@import 以及 Clang Modules 的工作原理。本文将继续从 Swift 编译器的角度,讲解 Swift 中 import 的语法、模块查找机制、底层实现原理以及与 Objective-C 的互操作。 从 Clang Module 到 Swift Module 在深入 Swift 的 import 机制之前,让我们先回顾一下上一篇文章中 Clang Module 的核心概念。 Clang Module 核心概念回顾 在 Objective-C 文章中,我们学习了 Clang Module 的几个关键特性: module.modulemap:定义模块结构的配置文件 framework module Foundation { umbrella header "Foundation.h" export * } 预编译模块缓存(.pcm 文件):Clang 将模块编译为二进制格式,缓存以加速后续编译 懒加载机制:只加载实际使用的声明,未使用的部分不会被解析 自动链接:@import 会自动链接对应的库,无需手动配置 模块查找路径:通过 -I、-F 等参数指定搜索路径 Swift 如何继承 Clang Module 的设计 Swift 的模块系统并非从零开始设计,而是站在 Clang Module 的肩膀上,继承了其核心优势,并针对 Swift 的特性进行了扩展: ...

May 2, 2026

SIL(Swift Intermediate Language)

SIL(Swift Intermediate Language,Swift 中间语言)是 Swift 编译器在编译过程中生成的一种中间表示(Intermediate Representation,IR)。它位于 Swift 源码与 LLVM IR 之间,是 Swift 编译器架构中至关重要的一层。 SIL 的设计目标是: 对 Swift 语言的高级语义进行精确建模 在高层抽象的基础上执行强大的优化 保留足够的类型信息,用于诊断和安全检查 作为 Swift 特有优化的载体,弥补 LLVM IR 无法直接表达 Swift 语义的不足 SIL 并不是给开发者日常编写的语言,而是编译器的内部表示。理解它有助于深入理解 Swift 的编译过程、性能优化原理和内存管理机制。 Swift 编译流程概览 在了解 SIL 之前,需要先理解 Swift 的整体编译流程: flowchart TD A[Swift 源代码 .swift] --> B[词法分析 Lexer] B --> C[语法分析 Parser] C --> D[语义分析 Sema] D --> E[SILGen: 生成 Raw SIL] E --> F[SIL 优化 Guaranteed Passes] F --> G[SIL 优化 General Passes] G --> H[IRGen: 生成 LLVM IR] H --> I[LLVM 优化] I --> J[机器码 .o] J --> K[链接器 Linker] K --> L[可执行文件 / 动态库] 各阶段简述 词法分析(Lexer) 编译的第一步。Lexer 逐字符扫描 .swift 源文件,将其切分为一系列Token(词法单元),例如关键字 func、标识符 add、运算符 +、字面量 42、左右括号等。Token 是后续所有分析的最小输入单位。这一步会剥离注释和空白,但保留它们的位置信息以便后续生成精确的诊断信息(错误提示的行号和列号)。 ...

May 2, 2026

Objective-C底层原理 - NSObject

本文将深入探讨Objective-C中所有对象的基类NSObject的底层实现原理,涵盖对象本质、isa指针机制、Tagged Pointer、引用计数、内存布局等核心概念。 NSObject 的本质:C 结构体 在Objective-C的底层实现中,NSObject的本质是一个C结构体。在苹果运行时源码中可以看到: // 底层运行时定义 struct objc_object { Class isa; // 指向对象所属类的指针 }; // 类型别名定义 typedef struct objc_object NSObject; 这意味着,当我们通过NSObject *obj = [[NSObject alloc] init];创建一个 NSObject 实例时,实际上在堆上分配了一个struct objc_object结构体,而变量obj只是一个指向该结构体的指针。 // 这行代码在底层实际上做了类似下面的事情: NSObject *obj = [[NSObject alloc] init]; // 等效的伪C代码: // 1. 调用 alloc 类方法,其核心是调用 malloc 在堆上分配一块足够大的内存 // 大小至少是 sizeof(struct objc_object),即一个isa指针的大小 NSObject *obj = (NSObject *)malloc(sizeof(struct objc_object)); // 2. 初始化这块内存,最重要的是设置 isa 指针,让它指向 NSObject 这个类 obj->isa = [NSObject class]; // 实际过程更复杂,但本质如此 // 3. 其他初始化工作 (init) 那么这里提到的isa指针是什么呢? ...

May 2, 2026

Mach-O的链接、装载与库

本文深入介绍iOS/macOS开发中的Mach-O文件格式、动态库与静态库,帮助理解程序的链接、装载过程,以及Rebase/Bind的底层原理。 一、基础概念 在深入学习之前,先了解几个核心概念。 1.0 从源代码到可执行文件 一段源代码要变成可执行文件,需要经历以下几个阶段: flowchart TB A["源代码 (.m/.swift)"] --> B["预处理展开宏、处理#import/#include"] B --> C["编译源代码 → 汇编代码"] C --> D["汇编汇编代码 → 目标文件(.o)"] D --> E["链接目标文件 + 库 → 可执行文件"] E --> F["可执行文件"] 1.1 什么是符号(Symbol) 符号是程序中函数、变量、类等实体的名称标识。当你写下一个函数调用时,编译器需要知道这个函数在哪里,符号就是用来定位它的。 // 这段代码中包含多个符号 void myFunction(void) { // 定义了符号 _myFunction NSLog(@"Hello"); // 引用了外部符号 _NSLog } int globalVar = 10; // 定义了符号 _globalVar 符号分为两类: 已定义符号:在当前编译单元(即当前源文件编译生成的.o文件)中有具体代码或数据定义的符号(如上例中的 _myFunction 和 _globalVar) 未定义符号:当前编译单元只是引用,实际定义在其他编译单元或库中的符号(如上例中的 _NSLog),需要在链接时解析 1.2 什么是链接(Linking) 链接是将多个编译后的目标文件(.o)组合成一个可执行文件的过程。链接器的核心工作是符号解析——把所有"未定义符号"与其他编译单元中的"已定义符号"关联起来。 flowchart LR subgraph 输入 A["main.o已定义: _main未定义: _foo"] B["foo.o已定义: _foo未定义: _NSLog"] C["Foundation已定义: _NSLog"] end A --> D["链接器符号解析"] B --> D C --> D D --> E["可执行文件所有符号已解析"] 符号解析的过程 链接器会遍历所有输入的目标文件和库,执行以下步骤: ...

May 2, 2026

KVO底层原理

基础概念 KVO(Key-Value Observing,键值观察)是 Apple 基于 KVC 实现的一种观察者模式,允许对象监听另一个对象特定属性的变化。当被观察对象的属性值发生改变时,观察者会收到通知。 // 注册观察 [person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; // 接收通知 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context { if ([keyPath isEqualToString:@"name"]) { NSLog(@"name changed: %@ → %@", change[NSKeyValueChangeOldKey], change[NSKeyValueChangeNewKey]); } } // 移除观察 - (void)dealloc { [person removeObserver:self forKeyPath:@"name"]; } KVO 的底层实现原理:isa-swizzling KVO 的核心实现机制是 isa-swizzling(isa 指针替换)。当对象被添加观察者时,Runtime 会在运行时动态创建该对象所属类的一个子类,并将对象的 isa 指针指向这个新的子类。 完整流程 flowchart TD A["addObserver:forKeyPath:"] --> B["Runtime 动态创建子类NSKVONotifying_ClassName"] B --> C["重写被观察属性的 setter 方法"] B --> D["重写 class 方法返回原始类"] B --> E["重写 dealloc 方法"] B --> F["重写 _isKVOA 方法返回 YES"] C --> G["将对象的 isa 指向NSKVONotifying_ClassName"] G --> H["当属性值改变时"] H --> I["调用 willChangeValueForKey:"] I --> J["调用原始 setter 方法"] J --> K["调用 didChangeValueForKey:"] K --> L["通知所有观察者"] 第一步:动态创建子类 当首次对某个类的实例调用 addObserver:forKeyPath:options:context: 时,Runtime 会: ...

May 2, 2026

KVC底层原理

基础概念 KVC(Key-Value Coding,键值编码)是 Apple 提供的一种通过字符串 key 间接访问对象属性的机制,定义在 NSKeyValueCoding 协议中,NSObject 默认遵循该协议。 // 直接访问 person.name = @"Tom"; NSString *name = person.name; // KVC 访问 [person setValue:@"Tom" forKey:@"name"]; NSString *name = [person valueForKey:@"name"]; KVC 的核心价值在于:通过字符串动态访问属性,不需要在编译期知道属性名。这使得字典转模型、序列化/反序列化、Interface Builder 的 Runtime Attributes 等场景成为可能。 setValue:forKey: 的底层流程 当调用 [obj setValue:value forKey:@"name"] 时,Runtime 按照以下顺序查找: flowchart TD A["setValue:forKey:@'name'"] --> B{"查找 setter 方法"} B -->|"找到"| C["调用 setter"] B -->|"未找到"| D{"accessInstanceVariablesDirectly返回 YES?"} D -->|"YES"| E{"按顺序查找实例变量_name → _isName → name → isName"} D -->|"NO"| F["调用 setValue:forUndefinedKey:默认抛出 NSUndefinedKeyException"] E -->|"找到"| G["直接设置实例变量值"] E -->|"未找到"| F 第一步:查找 setter 方法 Runtime 按以下顺序查找 setter 方法: ...

May 2, 2026