编程进阶网 编程进阶网
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机的原理
  • 操作系统原理
  • 网络协议原理
  • 数据库的原理
  • 序卷导读
  • 数据本质
  • 运行模型
  • 并发设计
  • 内存真相
  • 交互系统
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 技能之旅
  • 体系建设
  • 代码品质
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 性能优化
  • 数据结构导论
  • 线性结构详解
  • 树哈希结构论
  • 容器设计实战
  • 经典算法思想
  • 工程案例剖析
  • 算法题库精练
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript案例
  • JavaScript高级
  • Kotlin精通
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 毛选解读
  • 自我精进
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机的原理
  • 操作系统原理
  • 网络协议原理
  • 数据库的原理
  • 序卷导读
  • 数据本质
  • 运行模型
  • 并发设计
  • 内存真相
  • 交互系统
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 技能之旅
  • 体系建设
  • 代码品质
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 性能优化
  • 数据结构导论
  • 线性结构详解
  • 树哈希结构论
  • 容器设计实战
  • 经典算法思想
  • 工程案例剖析
  • 算法题库精练
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript案例
  • JavaScript高级
  • Kotlin精通
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 毛选解读
  • 自我精进
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • Android提升进阶

  • iOS开发和进阶

    • README
    • ObjC编程入门

      • README
      • 1.基础语法快速入门
        • 1.1 Objective-C
          • 1.1.1 ObjC简单说明
          • 1.1.2 ObjC文件说明
          • 1.1.3 HelloWorld
          • 1.1.4 理解OC中指针
          • 1.1.5 import导包介绍
          • 1.1.6 ObjC相关术语
          • 1.1.7 核心学习概念
          • 1.1.8 OC与其他语言对比
        • 1.2 消息传递的设计
          • 1.2.1 理解消息传递
          • 1.2.2 C++和OC区别
          • 1.2.3 用一个案例展示
          • 1.2.4 二种风格优劣
          • 1.2.5 消息传递底层解析
        • 1.3 字符串设计和使用
          • 1.3.1 字符串介绍
          • 1.3.2 字符串的创建
          • 1.3.3 常用字符串操作
        • 1.4 基本数据的使用
          • 1.4.1 常见的基础类型
          • 1.4.2 常见的集合类型
          • 1.4.3 NSArray
          • 1.4.4 NSDictionary
        • 1.5 控制结构
          • 1.5.1 条件控制结构
          • 1.5.2 循环控制结构
          • 1.5.3 跳转控制结构
          • 1.5.4 其他控制结构
        • 1.6 消息传递的陷阱与最佳实践
          • 1.6.1 unrecognized selector 崩溃
          • 1.6.2 nil 消息的安全特性
        • 1.7 集合类型的深度使用
          • 1.7.1 遍历中修改
          • 1.7.2 nil-value 崩溃
          • 1.7.3 深拷贝与浅拷贝
      • 面向对象编程
      • ObjC高级语法
      • ObjC内存管理
    • Swift编程入门

    • 专栏博客

  • Web开发和进阶

  • Linux应用开发

  • IoT智能硬件开发

  • Apps
  • iOS开发和进阶
  • ObjC编程入门
杨充
2015-07-22
目录

1.基础语法快速入门

# 01.基础语法快速入门

# 目录介绍

  • 1.1 Objective-C
    • 1.1.1 ObjC简单说明
    • 1.1.2 ObjC文件说明
    • 1.1.3 HelloWorld
    • 1.1.4 理解OC中指针
    • 1.1.5 import导包介绍
    • 1.1.6 ObjC相关术语
    • 1.1.7 核心学习概念
    • 1.1.8 OC与其他语言对比
  • 1.2 消息传递的设计
    • 1.2.1 理解消息传递
    • 1.2.2 C++和OC区别
    • 1.2.3 用一个案例展示
    • 1.2.4 二种风格优劣
    • 1.2.5 消息传递底层解析
  • 1.3 字符串设计和使用
    • 1.3.1 字符串介绍
    • 1.3.2 字符串的创建
    • 1.3.3 常用字符串操作
  • 1.4 基本数据的使用
    • 1.4.1 常见的基础类型
    • 1.4.2 常见的集合类型
    • 1.4.3 NSArray
    • 1.4.4 NSDictionary
  • 1.5 控制结构
    • 1.5.1 条件控制结构
    • 1.5.2 循环控制结构
    • 1.5.3 跳转控制结构
    • 1.5.4 其他控制结构
  • 1.6 消息传递的陷阱与最佳实践
    • 1.6.1 unrecognized selector 崩溃
    • 1.6.2 nil 消息的安全特性
  • 1.7 集合类型的深度使用
    • 1.7.1 遍历中修改
    • 1.7.2 nil-value 崩溃
    • 1.7.3 深拷贝与浅拷贝

# 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项目中常用的拓展名如下:

  1. .h 头文件。头文件包含类,类型,函数和常数的声明。
  2. .m 源代码文件。这是典型的源代码文件扩展名,可以包含 Objective-C 和 C 代码。
  3. .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区别

此二种风格的主要差异在于调用方法/消息传递这个动作。

  1. C++里类别与方法的关系严格清楚,一个方法必定属于一个类别,而且在编译时(compile time)就已经紧密绑定,不可能调用一个不存在类别里的方法。
  2. 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 动态性的三个实际价值:

  1. @selector 统一回调:[button addTarget:self action:@selector(onTap) ...]——不需要函数指针、不需要 lambda、不需要接口,一个选择器解决一切回调
  2. KVO / KVC:运行时动态添加观察者、动态读写属性——修改一行代码就能监听任意对象的任意属性变化
  3. 运行时热修复:线上 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 面向对象的设计原理与最佳实践。

上次更新: 2026/07/12, 19:39:51
README
面向对象编程

← README 面向对象编程→

最近更新
01
audit
07-27
02
C++入门教程全章思考题汇编
07-24
03
12.技术团队建设能力
07-21
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 鄂ICP备2024073355号-1 | 鄂ICP备2024073355号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式