1.基础语法快速入门
# 01.基础语法快速入门
# 目录介绍
# 1.1 Objective-C
# 1.1.1 ObjC简单说明
Objective-C,简称OC,是一种通用、高级、面向对象的编程语言。它扩展了标准的ANSI C编程语言,将Smalltalk式的消息传递机制加入到ANSI C中。当前主要支持的编译器有GCC和Clang(采用LLVM作为后端)。
Objective-C的商标权属于苹果公司,苹果公司也是这个编程语言的主要开发者。
Objective-C是C语言的严格超集。这意味着任何C语言程序不经修改就可以直接通过Objective-C编译器编译。
Objective-C 的三个设计哲学:
| 哲学 | 来源 | 体现 |
|---|---|---|
| C 语言兼容 | ANSI C | 所有 C 代码可直接编译为 OC;OC 代码可以混写 C 语句 |
| Smalltalk 消息传递 | Smalltalk-80 | [obj method: arg] 方括号语法,运行时动态绑定 |
| 动态运行时 | NeXTStep | isa 指针、objc_msgSend、Method Swizzling |
一句话总结:OC 是「在 C 语言上嫁接了一套 Smalltalk 风格的对象系统」——既保留了 C 的性能和控制力,又获得了运行时的动态灵活性。
# 1.1.2 ObjC文件说明
Objective-C的原意就是在C语言主体上加入面向对象的特性。OC项目中常用的拓展名如下:
- .h 头文件。头文件包含类,类型,函数和常数的声明。
- .m 源代码文件。这是典型的源代码文件扩展名,可以包含 Objective-C 和 C 代码。
- .mm 源代码文件。带有这种扩展名的源代码文件,除了可以包含Objective-C和C代码以外还可以包含C++代码。仅在你的Objective-C代码中确实需要使用C++类或者特性的时候才用这种扩展名。
文件扩展名规则速查:
| 扩展名 | 可用语言 | 典型用途 | 编译器 |
|---|---|---|---|
.h | C / OC | 头文件声明 | Clang |
.m | C / OC | OC 实现文件 | Clang |
.mm | C / OC / C++ | 混合 C++ 的 OC 实现 | Clang (ObjC++ mode) |
.c | C | 纯 C 源文件 | Clang / GCC |
.cpp | C++ | 纯 C++ 源文件 | Clang++ |
# 1.1.3 HelloWorld
学习任何一门语言之前,基本都需要做的就是编写并运行一个HelloWorld程序,对于OC而言则是如下:
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[]) {
@autoreleasepool {
NSLog (@"Hello, World!");
}
return 0;
}
逐行解读——为什么每个部分必不可少:
#import <Foundation/Foundation.h> → 导入基础框架(NSString、NSLog、NSArray 等)
@autoreleasepool {} → 创建自动释放池(OC 内存管理的核心机制)
NSLog(@"Hello, World!") → 类似 printf,但支持 %@ 格式化对象
@ 字符 → OC 特有的字符串字面量前缀
使用clang进行编译:
clang -framework Foundation hello.m -o hello
运行:
$ ./hello
2024-04-05 09:33:22.579 hello[75742:3312942] Hello, World!
# 1.1.4 理解OC中指针
当我们使用指针时,我们其实是在引用一个对象的地址,而不是直接使用堆 (heap) 中创建的对象,这样,当我们传递对象并且对象被改变时,由于使用的是引用,我们能够得到改变后的对象。
由于 Objective-C 是一门面向对象的语言,当我们创建一个对象时,大多数时候都应该使用指针。
OC 对象的本质——堆分配 + 指针引用:
栈(Stack) 堆(Heap)
┌──────────┐ ┌──────────────┐
│ obj 指针 │──────────→ │ 实际对象数据 │
│ (8 bytes)│ │ (isa + 成员) │
└──────────┘ └──────────────┘
OC 中所有对象都在堆上分配,栈上只存指针。
这与 C++ 可以在栈上创建对象(MyClass obj;)不同。
# 1.1.5 import导包介绍
在 C 中使用 #include 来导入其他文件,在 OC 中使用 #import 来导入其他文件,不同点在于 #import 可以保证文件不被重复导入。
#import <Foundation/Foundation.h> // 系统框架(尖括号)
#import "MyClass.h" // 自定义头文件(双引号)
#import vs #include vs @import:
| 指令 | 重复导入保护 | 编译速度 | 适用 |
|---|---|---|---|
#include | ❌ 需手动 #ifndef 保护 | 慢(多次解析) | 纯 C |
#import | ✅ 自动 | 中 | OC(传统) |
@import | ✅ 自动 | 快(预编译模块) | OC(现代,iOS 7+) |
#import <Foundation/Foundation.h> 的意思:告诉编译器查找 Foundation 框架中的 Foundation.h 文件。通过在主文件的头部使用 #import 来使用框架提供的功能。
# 1.1.6 ObjC相关术语
OC 相关的一些术语:
- 类(class):是一种表示对象类型的结构体。对象通过它的类来获取自身的各种信息,尤其是执行每个操作需要运行的代码
- 对象(object):是一种包含值和指向其类的隐藏指针的结构体
- 实例(instance):对象的另一种称呼,例如 circle 对象是 Circle 类的实例
- 消息(message):是对象可以执行的操作,用于通知对象去做什么,对象在接收到消息后将查询对应的类,以便找到正确的代码来运行
- 方法(method):为了响应消息而执行的代码
- 方法调度(method dispatcher):OC 的一种机制,用于推测执行什么方法以响应某个特定的消息,后面会有章节专门讲解 OC 方法调度机制
- 接口(interface):是类为对象提供的的特性描述
- 实现(implementation):是使接口能正常工作的代码,实际就是类的具体实现代码
# 1.1.7 核心学习概念
- 面向对象扩展:基于 C 语言的面向对象超集,添加 Smalltalk 式消息传递。
- 动态运行时:允许运行时修改类和方法(反射、方法调配)。
- 兼容性:兼容标准 C 语法,可直接嵌入 C 代码。
- 内存管理:支持手动引用计数(MRC)和自动引用计数(ARC)。
开发环境
- 工具:Xcode(IDE)、Clang(编译器)、LLDB(调试器)
- 框架依赖:Foundation(基础类库)、UIKit(iOS)、AppKit(macOS)
# 1.1.8 OC与其他语言对比
为什么苹果坚持了 OC 近 30 年才推出 Swift? 因为 OC 有一项其他主流语言没有的能力——运行时修改。
| 特性 | Objective-C | Swift | C++ | Java |
|---|---|---|---|---|
| 范式 | 面向对象 + 消息传递 | 面向协议 + 面向对象 | 多范式 | 面向对象 |
| 动态性 | 极高(运行时) | 低(静态为主) | 低(编译时) | 中(反射) |
| 方法调用 | objc_msgSend | 虚函数表 / 静态 | 虚函数表 | 虚函数表 |
| 内存管理 | ARC(编译器自动) | ARC | 手动/智能指针 | GC |
| C 兼容 | ✅ 直接混写 | ❌ 需桥接 | ✅ | ❌ |
| 编译速度 | 快 | 慢 | 中 | 快 |
核心差异:OC 的方法调用本质上是消息发送,而非函数调用。这是区别于其他所有主流语言的根本特征——也是 OC 最大灵活性和最大风险的来源。
# 1.2 消息传递的设计
# 1.2.1 理解消息传递
Objective-C最大的特色是承自Smalltalk的消息传递模型(message passing),此机制与今日C++式之主流风格差异甚大。
Objective-C里,与其说对象互相调用方法,不如说对象之间互相传递消息更为精确。消息传递是一种对象之间进行通信的机制。
消息传递的四个要素:
[receiver message: argument]
receiver → 接收者(对象指针)
message → 选择器(方法名)
argument → 参数
返回值 → 方法的返回结果
消息传递 ≠ 函数调用:在 C++ 中,obj->method() 在编译期就确定了跳转地址;在 OC 中,[obj method] 在运行时才通过 objc_msgSend 查找并调用对应的方法。
# 1.2.2 C++和OC区别
此二种风格的主要差异在于调用方法/消息传递这个动作。
- C++里类别与方法的关系严格清楚,一个方法必定属于一个类别,而且在编译时(compile time)就已经紧密绑定,不可能调用一个不存在类别里的方法。
- Objective-C,类别与消息的关系比较松散,调用方法视为对对象发送消息,所有方法都被视为对消息的回应。
C++里,送一个消息给对象(或者说调用一个方法)的语法如下:
obj.method(argument);
Objective-C则写成:
[obj method: argument];
注意:所有消息处理直到运行时(runtime)才会动态决定,并交由类别自行决定如何处理收到的消息。
也就是说,一个类别不保证一定会回应收到的消息,如果类别收到了一个无法处理的消息,程序只会抛出异常,不会出错或崩溃。
编译期绑定 vs 运行期绑定的核心区别:
| C++ 虚函数 | OC 消息传递 | |
|---|---|---|
| 绑定时机 | 编译期(vtable) | 运行期(objc_msgSend) |
| 方法不存在 | 编译错误 | 运行时异常(可转发) |
| nil 处理 | 未定义崩溃 | 安全忽略 |
| 方法替换 | 不支持 | Method Swizzling |
| 性能 | 极快(一次间接跳转) | 中(缓存加速后接近) |
# 1.2.3 用一个案例展示
展示如何使用消息传递调用对象的方法:
NSString *string = @"Hello, World!";
// 使用消息传递调用方法
NSString *result = [string stringByAppendingString:@"yc"];
NSLog(@"%@", result); // 输出:Hello, World!yc
创建了一个NSString对象,并将其赋值为Hello, World!。然后,我们使用 stringByAppendingString:方法发送消息给string对象拼接字符串。
请注意,使用消息传递时,编译器不会进行静态类型检查,因此需要确保消息的接收者对象确实能够响应所发送的消息,否则会导致运行时错误。
编译器视角 vs 运行时视角:
编译器看到的:
[string stringByAppendingString:@"yc"]
→ 检查 string 是 id/NSString* 类型 → 允许通过
运行时发生的事情:
objc_msgSend(string, @selector(stringByAppendingString:), @"yc")
→ 查 string 的 isa → 找方法缓存 → 没命中 → 遍历方法列表
→ 找到 stringByAppendingString: 的实现 → 调用 → 返回新字符串
# 1.2.4 二种风格优劣
C++强制要求所有的方法都必须有对应的动作,且编译期绑定使得函数调用非常快速。缺点是仅能借由virtual关键字提供有限的动态绑定能力。
Objective-C天生即具备鸭子类型之动态绑定能力,因为运行期才处理消息,允许发送未知消息给对象。可以送消息给整个对象集合而不需要一一检查每个对象的类型,也具备消息转送机制。同时空对象nil接受消息后默认为不做事,所以送消息给nil也不用担心程序崩溃。
OC 动态性的三个实际价值:
- @selector 统一回调:
[button addTarget:self action:@selector(onTap) ...]——不需要函数指针、不需要 lambda、不需要接口,一个选择器解决一切回调 - KVO / KVC:运行时动态添加观察者、动态读写属性——修改一行代码就能监听任意对象的任意属性变化
- 运行时热修复:线上 Bug 可以通过 Method Swizzling 替换有问题的实现——这是 JSPatch 等热修复框架的底层原理
# 1.2.5 消息传递底层解析
objc_msgSend 是 OC 方法调用的底层 C 函数——所有 [obj method] 最终都会被编译为对这个函数的调用:
// C 语言视角的 OC 方法调用
objc_msgSend(obj, @selector(method), arg1, arg2);
完整的消息发送流程(含缓存):
[obj doSomething]
↓
objc_msgSend(obj, @selector(doSomething))
↓
├─ obj == nil? → 直接返回 nil/0 ← 消息无操作
│
└─ obj != nil
↓
查 isa → class
↓
查方法缓存 (cache_t) ← O(1) 哈希查找
├─ 命中 → 直接跳转 (最快路径,90%+ 命中率)
└─ 未命中
↓
查方法列表 (method_list_t) ← O(N) 遍历
├─ 找到 → 加入缓存 → 跳转
└─ 未找到
↓
查父类 (superclass) ← 沿继承链递归查找
├─ 找到 → 加入本类缓存 → 跳转
└─ 未找到
↓
消息转发流程 (三次机会)
├─ ① resolveInstanceMethod: (动态添加方法)
├─ ② forwardingTargetForSelector: (转发给其他对象)
└─ ③ forwardInvocation: (完整转发)
└─ 仍无法处理 → unrecognized selector 崩溃
为什么 OC 的 objc_msgSend 用汇编实现? 因为 C 函数无法在运行时动态知道参数个数和类型(变参不算),而 OC 的每个方法参数签名不同——必须用汇编直接操作寄存器来转发参数。
# 1.3 字符串设计和使用
# 1.3.1 字符串介绍
作为C语言的超集,Objective-C 支持 C 语言字符串方面的约定。也就是说,单个字符被单引号包括,字符串被双引号包括。
然而,大多数Objective-C通常不使用C语言风格的字符串。反之,大多数框架把字符串传递给NSString对象。
NSString类提供了字符串的类包装,包含了所有你期望的优点,包括对保存任意长度字符串的内建内存管理机制,支持Unicode,printf风格的格式化工具,等等。
因为这种字符串使用的非常频繁,Objective-C提供了一个助记符@可以方便地从常量值创建NSString对象。
# 1.3.2 字符串的创建
// 从一个C语言字符串创建Objective-C字符串
NSString* fromCString = [NSString stringWithCString:"A C string"
encoding:NSASCIIStringEncoding];
// 使用助记符@
NSString* name = @"PANN";
NSString* line = [NSString stringWithFormat:@"Hello, %s\n", @"String"];
// 格式化拼接(类似 printf)
NSString* info = [NSString stringWithFormat:@"Name: %@, Age: %ld", name, (long)25];
C 字符串 vs NSString:
char * (C 字符串) | NSString * (OC) | |
|---|---|---|
| 编码 | ASCII / UTF-8(手动处理) | Unicode 原生支持 |
| 内存 | 手动管理 | ARC 自动管理 |
| 不可变 | ❌ 可修改 | ✅ 不可变(NSMutableString 可变) |
| 格式化 | sprintf | stringWithFormat: |
| @前缀 | 无 | @"hello" |
# 1.3.3 常用字符串操作
// 拼接
NSString *greeting = [@"Hello" stringByAppendingString:@" World"];
// 替换
NSString *result = [@"abcabc" stringByReplacingOccurrencesOfString:@"a" withString:@"X"];
// result = "XbcXbc"
// 分割
NSArray *parts = [@"a,b,c" componentsSeparatedByString:@","];
// parts = @[@"a", @"b", @"c"]
// 大小写
NSString *upper = [@"hello" uppercaseString]; // "HELLO"
NSString *lower = [@"HELLO" lowercaseString]; // "hello"
// 判断前缀/后缀
BOOL hasHttp = [@"http://apple.com" hasPrefix:@"http"];
BOOL isImage = [@"photo.png" hasSuffix:@".png"];
// 比较
BOOL equal = [@"abc" isEqualToString:@"abc"]; // ✅ 内容比较
// 注意:== 比较指针地址,不要用来比较字符串内容!
// 可变字符串
NSMutableString *mstr = [@"Hello" mutableCopy];
[mstr appendString:@" World"]; // "Hello World"
[mstr insertString:@"Beautiful " atIndex:6]; // "Hello Beautiful World"
易错提醒:NSString 比较内容用 isEqualToString:,不要用 ==!== 比较的是两个指针是否指向同一内存地址。
# 1.4 基本数据的使用
# 1.4.1 常见的基础类型
Objective-C 在 C 语言基础类型之上,增加了一套以 NS 为前缀的类型体系:
| C 类型 | OC 类型 | 说明 |
|---|---|---|
int | NSInteger | 32/64 位自适应整数 |
unsigned int | NSUInteger | 无符号整数 |
float | CGFloat | 32/64 位自适应浮点 |
double | double | 同为 double |
BOOL (C 没有) | BOOL | YES / NO |
| - | NSNumber | 包装任意数值为对象 |
| - | NSValue | 包装 C 结构体为对象 |
int age = 25;
float height = 1.75;
double pi = 3.14159;
char initial = 'A';
BOOL isStudent = YES; // BOOL 是 Objective-C 的布尔类型
为什么有 NSNumber? OC 的集合(NSArray、NSDictionary)只能存对象——不能直接存 int。NSNumber 把基本类型包装成对象:
NSNumber *num = @42; // 字面量语法
NSArray *scores = @[@95, @87, @72]; // 分数数组
# 1.4.2 常见的集合类型
OC 的集合体系(底层都是 C 结构体实现,但对外暴露面向对象接口):
NSObject
├─ NSArray (不可变有序集合)
│ └─ NSMutableArray (可变)
├─ NSDictionary (不可变键值对)
│ └─ NSMutableDictionary (可变)
├─ NSSet (不可变无序集合)
│ └─ NSMutableSet (可变)
└─ NSData (不可变二进制数据)
└─ NSMutableData (可变)
| 集合类型 | 有序 | 可重复 | 可变版本 | 典型场景 |
|---|---|---|---|---|
NSArray | ✅ | ✅ | NSMutableArray | 列表数据 |
NSDictionary | ❌ | Key 不可重复 | NSMutableDictionary | JSON/配置 |
NSSet | ❌ | ❌ | NSMutableSet | 去重/成员判断 |
NSData | - | - | NSMutableData | 文件/网络数据 |
# 1.4.3 NSArray
Objective-C 中的 NSArray 是一种常用的集合类,用于存储有序的对象集合。
NSArray *anArray = @[@1, @2L, @3.0F, @4U, @"5"];
NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:2];
[mutableArray addObject:@"Hello"];
[mutableArray addObject:@"World"];
[mutableArray removeObjectAtIndex:0];
NSLog(@"%@", [mutableArray objectAtIndex:0]);
性能特征:NSArray 底层是环形缓冲区——头尾插入 O(1),中间插入 O(N)。
# 1.4.4 NSDictionary
字典,类似于其它编程语言中的 Map 的数据类型。
NSDictionary<NSString *, NSObject *> *dictionary =
@{ @"name" : @"Objective-C", @"birth" : @1992 };
NSObject *dName = dictionary[@"name"];
NSLog(@"dictionary = %@", [dictionary description]);
字典的字面量语法:
// 旧式写法
NSDictionary *d1 = [NSDictionary dictionaryWithObjectsAndKeys:
@"Objective-C", @"name", @1992, @"birth", nil];
// 新式字面量(推荐)
NSDictionary *d2 = @{@"name": @"Objective-C", @"birth": @1992};
# 1.5 控制结构
# 1.5.1 条件控制结构
if 语句,用于根据条件执行不同的代码块。
int a = 10;
if (a > 5) {
NSLog(@"a is greater than 5");
} else if (a == 5) {
NSLog(@"a is equal to 5");
} else {
NSLog(@"a is less than 5");
}
switch 语句,用于多条件分支选择。
int day = 3;
switch (day) {
case 1:
NSLog(@"Monday");
break;
case 2:
NSLog(@"Tuesday");
break;
case 3:
NSLog(@"Wednesday");
break;
default:
NSLog(@"Invalid day");
break;
}
OC 中 if/switch 与标准 C 完全相同——因为 OC 是 C 的超集。
# 1.5.2 循环控制结构
for 循环,用于重复执行代码块,通常用于已知循环次数的情况。
for (int i = 0; i < 5; i++) {
NSLog(@"i = %d", i);
}
while 循环,在条件为真时重复执行代码块。
int i = 0;
while (i < 5) {
NSLog(@"i = %d", i);
i++;
}
do-while 循环,先执行一次代码块,然后在条件为真时重复执行。
int i = 0;
do {
NSLog(@"i = %d", i);
i++;
} while (i < 5);
for-in 循环,用于遍历集合(如数组、字典等)。
NSArray *array = @[@"Apple", @"Banana", @"Orange"];
for (NSString *fruit in array) {
NSLog(@"Fruit: %@", fruit);
}
// 遍历字典
NSDictionary *dict = @{@"a": @1, @"b": @2};
for (NSString *key in dict) {
NSLog(@"%@ = %@", key, dict[key]);
}
// 使用 enumerate 同时获取索引和元素
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSLog(@"%lu: %@", idx, obj);
if (idx == 1) *stop = YES; // 提前终止
}];
for-in vs enumerateObjectsUsingBlock:
| for-in | enumerate block | |
|---|---|---|
| 性能 | 最快 | 稍慢(block 开销) |
| 提前终止 | break | *stop = YES |
| 并发遍历 | ❌ 需手动 | NSEnumerationConcurrent |
| 代码风格 | 命令式 | 函数式 |
# 1.5.3 跳转控制结构
break 语句,用于立即退出循环或 switch 语句。
for (int i = 0; i < 10; i++) {
if (i == 5) {
break; // 退出循环
}
NSLog(@"i = %d", i);
}
continue 语句,用于跳过当前循环的剩余部分,直接进入下一次循环。
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
continue; // 跳过偶数
}
NSLog(@"i = %d", i);
}
return 语句,用于从函数中返回值并退出函数。
- (int)add:(int)a to:(int)b {
return a + b;
}
# 1.5.4 其他控制结构
@autoreleasepool,用于管理内存,自动释放对象。
@autoreleasepool {
NSString *str = [NSString stringWithFormat:@"Hello, %@", @"World"];
NSLog(@"%@", str);
}
@synchronized,用于实现线程同步,确保代码块在同一时间只能被一个线程执行。
NSObject *lock = [[NSObject alloc] init];
@synchronized(lock) {
// 线程安全的代码块
}
# 1.6 消息传递的陷阱与最佳实践
ObjC 的消息传递机制(objc_msgSend)是其动态性的核心,但也带来了几个极易踩中的陷阱。
# 1.6.1 unrecognized selector 崩溃
向一个对象发送它不响应的方法,运行时会抛出经典的 unrecognized selector sent to instance 崩溃。这是 ObjC 开发中最常见的运行时错误。
// 假设 Person 类没有 fly 方法
Person *p = [[Person alloc] init];
[p fly]; // 💥 崩溃: unrecognized selector sent to instance
三种防御方式:
方式一:运行时检查。 使用 respondsToSelector: 在调用前判断对象是否响应某个方法。这是最推荐的防御方式。
if ([p respondsToSelector:@selector(fly)]) {
[p fly];
}
方式二:协议约束。 使用 id<MyProtocol> 限制对象必须实现协议中的方法,编译期就能发现缺失。
id<Flyable> flyer = someObject;
[flyer fly]; // 编译期保证 flyer 实现了 fly
方式三:消息转发。 重写 forwardingTargetForSelector: 或 forwardInvocation: 在崩溃前拦截未识别的方法,将消息转发给能处理的对象,或记录日志后安全降级。
# 1.6.2 nil 消息的安全特性
ObjC 中向 nil 发送消息不会崩溃,消息会被静默忽略,返回值默认为 0 / nil / NO。这是一把双刃剑:
Person *p = nil;
NSString *name = [p name]; // 不会崩溃,name 为 nil
NSInteger age = [p age]; // 不会崩溃,age 为 0
好的方面:代码简洁,省去了大量 if (obj != nil) 判断。危险的方面:有时你期望对象存在、期望返回有效值,但对象已经是 nil 了,编译器不报错、运行不崩溃——bug 就这样被无声地掩盖了。
最佳实践:对于"对象不应该是 nil"的关键路径,显式加 NSParameterAssert(obj) 断言,让问题在开发阶段暴露而不是静默带 bug 上线。
# 1.7 集合类型的深度使用
NSArray 和 NSDictionary 是 ObjC 中最常用的集合类型,但它们的几个特性常被忽视。
# 1.7.1 遍历中修改
在遍历可变数组的同时增删元素,会导致崩溃或数据错乱。这是新手最容易犯的错误之一。
NSMutableArray *arr = [@[@1, @2, @3] mutableCopy];
for (NSNumber *n in arr) {
if ([n intValue] == 2) {
[arr removeObject:n]; // 💥 遍历中修改,崩溃
}
}
正确做法:先收集需要操作的元素,遍历完成后再修改;或使用 enumerateObjectsUsingBlock 配合索引反向遍历。
# 1.7.2 nil-value 崩溃
字典的 key 和 value 都不能为 nil,插入 nil 会直接崩溃。
NSDictionary *dict = @{
@"name": person.name, // 如果 person.name 为 nil → 💥 崩溃
};
修复:使用 ?: 运算符提供默认值——person.name ?: @""。
# 1.7.3 深拷贝与浅拷贝
copy / mutableCopy 对集合对象只做浅拷贝(复制容器本身,不复制容器内的元素)。需要真正的深拷贝时,使用 initWithArray:copyItems: 或序列化/反序列化方案。
浅拷贝 vs 深拷贝图解:
浅拷贝(默认):
原数组 → [ptr1, ptr2, ptr3]
新数组 → [ptr1, ptr2, ptr3] ← 指向同一批对象!
深拷贝:
原数组 → [obj1, obj2, obj3]
新数组 → [obj1', obj2', obj3'] ← 各自独立的副本
下一篇预告:02.面向对象编程详解 —— 从类声明到继承体系,从方法调度到属性修饰符,完整覆盖 OC 面向对象的设计原理与最佳实践。