编程进阶网 编程进阶网
首页
  • 在线工具
  • 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.基础语法快速入门
      • 面向对象编程
      • ObjC高级语法
        • 3.1 枚举的设计和使用
          • 3.1.1 枚举的定义
          • 3.1.2 NSENUM与NSOPTIONS
          • 3.1.3 枚举的使用场景
          • 3.1.4 枚举定义值
        • 3.2 协议设计和使用
          • 3.2.1 协议的作用
          • 3.2.2 协议的声明
          • 3.2.3 协议使用步骤
          • 3.2.4 委托模式实战
          • 3.2.5 协议使用场景
          • 3.2.6 协议的判断
        • 3.3 理解动态类型设计
          • 3.3.1 理解动态类型
          • 3.3.2 理解id类型
          • 3.3.3 动态类型检查
          • 3.3.4 动态类型调用
          • 3.3.5 动态类型利弊
        • 3.4 类别如何使用
          • 3.4.1 理解类别机制
          • 3.4.2 类别使用场景
          • 3.4.3 类别使用案例
          • 3.4.4 类别关联对象
        • 3.5 Blocks设计
          • 3.5.1 Block是什么
          • 3.5.2 Block使用
        • 3.6 KVO(键值观察)
          • 3.6.1 添加观察者
          • 3.6.2 回调
          • 3.6.3 KVO实现原理
        • 3.7 多线程使用
          • 3.7.1 线程基本概念
          • 3.7.2 使用NSThread
          • 3.7.3 NSOperation
          • 3.7.4 GCD多线程
          • 3.7.5 线程安全
        • 3.8 Block 内存管理深入
          • 3.8.1 Block 的三种类型
          • 3.8.2 Block 修改外部变量
          • 3.8.3 Block 循环引用的防御清单
        • 3.9 GCD 实战模式总结
          • 3.9.1 串行队列做锁
          • 3.9.2 dispatch_once 实现单例
        • 3.10 综合案例:网络图片下载器
      • ObjC内存管理
    • Swift编程入门

    • 专栏博客

  • Web开发和进阶

  • Linux应用开发

  • IoT智能硬件开发

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

ObjC高级语法

# 03.ObjC高级语法

# 目录介绍

  • 3.1 枚举的设计和使用
    • 3.1.1 枚举的定义
    • 3.1.2 NS_ENUM与NS_OPTIONS
    • 3.1.3 枚举的使用场景
    • 3.1.4 枚举定义值
  • 3.2 协议与委托
    • 3.2.1 协议的作用
    • 3.2.2 协议的声明
    • 3.2.3 协议使用步骤
    • 3.2.4 委托模式实战
    • 3.2.5 协议使用场景
    • 3.2.6 协议的判断
  • 3.3 理解动态类型设计
    • 3.3.1 理解动态类型
    • 3.3.2 理解id类型
    • 3.3.3 动态类型检查
    • 3.3.4 动态类型调用
    • 3.3.5 动态类型利弊
  • 3.4 类别如何使用
    • 3.4.1 理解类别机制
    • 3.4.2 类别使用场景
    • 3.4.3 类别使用案例
    • 3.4.4 类别关联对象
  • 3.5 Blocks设计
    • 3.5.1 Block是什么
    • 3.5.2 Block使用
  • 3.6 KVO(键值观察)
    • 3.6.1 添加观察者
    • 3.6.2 回调
    • 3.6.3 KVO实现原理
  • 3.7 多线程使用
    • 3.7.1 线程基本概念
    • 3.7.2 使用NSThread
    • 3.7.3 NSOperation
    • 3.7.4 GCD多线程
    • 3.7.5 线程安全
  • 3.8 Block 内存管理深入
    • 3.8.1 Block 的三种类型
    • 3.8.2 Block 修改外部变量
    • 3.8.3 Block 循环引用的防御清单
  • 3.9 GCD 实战模式总结
    • 3.9.1 串行队列做锁
    • 3.9.2 dispatch_once 实现单例
  • 3.10 综合案例:网络图片下载器

# 3.1 枚举的设计和使用

# 3.1.1 枚举的定义

定义枚举类型:使用typedef enum关键字来定义枚举类型。在定义时,可以列出枚举的所有可能取值。

typedef enum {
    OptionA,
    OptionB,
    OptionC
} MyEnum;

枚举常量的默认值:如果没有为枚举常量指定具体的值,它们将按照顺序从0开始自动分配值。第一个常量的默认值为0,后续常量的值依次递增。

# 3.1.2 NS_ENUM与NS_OPTIONS

OC 提供两种枚举宏——NS_ENUM 和 NS_OPTIONS:

// NS_ENUM:互斥状态(一次只能选一个)
typedef NS_ENUM(NSInteger, UIControlState) {
    UIControlStateNormal    = 0,
    UIControlStateHighlighted,
    UIControlStateDisabled,
    UIControlStateSelected
};

// NS_OPTIONS:可组合的选项(可多位叠加)
typedef NS_OPTIONS(NSUInteger, UIViewAnimationOptions) {
    UIViewAnimationOptionLayoutSubviews   = 1 << 0,
    UIViewAnimationOptionAllowUserInteraction = 1 << 1,
    UIViewAnimationOptionBeginFromCurrentState = 1 << 2,
    UIViewAnimationOptionRepeat           = 1 << 3
};

两者区别:

NS_ENUM NS_OPTIONS
类型 NSInteger NSUInteger
语义 互斥单选 位掩码组合
底层 连续整数值 1 << n 位移
组合方式 不能组合 \| 位运算符
检查方式 == 比较 & 位运算
例子 状态、方向 配置选项、权限

位掩码用法:

// 组合多个选项
UIViewAnimationOptions options = UIViewAnimationOptionRepeat
                               | UIViewAnimationOptionAllowUserInteraction;

// 检查是否包含某个选项
if (options & UIViewAnimationOptionRepeat) {
    // 包含 Repeat
}

# 3.1.3 枚举的使用场景

应该用枚举来表示状态机的状态、传递给方法的选项以及状态码等值,给这些值起个易懂的名字。

用NS_ENUM与NS_OPTIONS宏来定义枚举类型, 并指明其底层数据类型.这样做可以确保枚举是用开发者所选的底层数据类型实现出来的,而不会采用编译器所选的类型。

# 3.1.4 枚举定义值

在 Objective-C 中,枚举(Enum)可以用来定义一组相关的命名常量。每个枚举常量都有一个与之相关联的整数值。

// 定义带值的枚举
typedef NS_ENUM(NSInteger, Weekday) {
    Monday = 1,
    Tuesday = 2,
    Wednesday = 3,
    Thursday = 4,
    Friday = 5,
    Saturday = 6,
    Sunday = 7
};

// switch 与枚举——编译器会检查穷尽性
Weekday today = Wednesday;
switch (today) {
    case Monday:    NSLog(@"周一"); break;
    case Tuesday:   NSLog(@"周二"); break;
    case Wednesday: NSLog(@"周三"); break;
    // 编译器不会强制要求 default(因为枚举是封闭集合)
}

# 3.2 协议设计和使用

# 3.2.1 协议的作用

协议(Protocol)是一种定义接口的机制,用于声明一组方法、属性或其他成员,以便其他类遵循(adopt)并实现(implement)。

协议的作用是定义了一套规范,使得不同的类可以遵循相同的接口,从而实现代码的重用和解耦。

协议 vs C++ 纯虚类 vs Java interface:

OC Protocol C++ 纯虚类 Java Interface
方法实现 声明不实现 声明不实现 声明不实现
可选方法 ✅ @optional ❌ 全部强制 ❌ (default method 为特例)
属性声明 ✅ @property ✅ ✅
多遵循 ✅ ❌ ✅
默认实现 ❌ ❌ ✅ (Java 8+)

# 3.2.2 协议的声明

//@protocol 是用于声明协议(Protocol)的关键字。协议定义了一组方法,用于描述类或对象应该遵循的行为和功能。
@protocol MyProtocol <NSObject>     // 继承 NSObject 协议,获得基本方法

//协议中的方法声明与类中的方法声明类似,使用以下语法:- (返回类型)方法名;

- (void) doSomething;
- (NSString *) getName;

//@required
@optional
- (NSString *) getAge;

@end

@required与@optional

  1. @required修饰的方法的声明,就必须实现,编译器会警告,默认属性
  2. @optional修饰的方法的声明,可以实现或者不实现,不实现编译器也不会警告

为什么委托常用 weak? 防止循环引用——A 持有 B,B 的 delegate 又指回 A。如果用 strong,两者永远不释放。

@property (nonatomic, weak) id<MyProtocol> delegate;  // ✅ 弱引用

# 3.2.3 协议使用步骤

在实践中,可以按照以下步骤使用协议:

  1. 定义协议:使用@protocol关键字定义协议,并在其中声明需要的方法、属性等。
  2. 遵循协议:在类的声明中使用来遵循特定的协议。
  3. 实现协议方法:在类的实现中,根据协议的要求实现相应的方法。
  4. 使用协议:通过协议类型的指针或引用,可以调用协议中定义的方法,而不需要关心具体的类。

# 3.2.4 委托模式实战

委托(Delegate)是 OC 协议最经典的应用。以下用"下载管理器通知下载进度"来展示完整模式:

// ── DownloadDelegate.h ──
@protocol DownloadDelegate <NSObject>
@optional
- (void)downloadDidStart:(NSString *)url;
- (void)download:(NSString *)url didProgress:(float)percent;
@required
- (void)downloadDidFinish:(NSString *)url data:(NSData *)data;
- (void)downloadDidFail:(NSString *)url error:(NSError *)error;
@end

// ── DownloadManager.h ──
@interface DownloadManager : NSObject
@property (nonatomic, weak) id<DownloadDelegate> delegate;   // ← weak 防循环引用
- (void)downloadURL:(NSString *)url;
@end

// ── DownloadManager.m ──
@implementation DownloadManager

- (void)downloadURL:(NSString *)url {
    // 检查 delegate 是否实现了可选方法
    if ([self.delegate respondsToSelector:@selector(downloadDidStart:)]) {
        [self.delegate downloadDidStart:url];
    }

    // 模拟下载过程
    for (int i = 1; i <= 100; i += 20) {
        usleep(100000);
        if ([self.delegate respondsToSelector:@selector(download:didProgress:)]) {
            [self.delegate download:url didProgress:i / 100.0];
        }
    }

    NSData *data = [@"fake data" dataUsingEncoding:NSUTF8StringEncoding];
    // 必须方法直接调用(协议中 @required)
    [self.delegate downloadDidFinish:url data:data];
}

@end

// ── 使用方 ──
@interface ViewController : UIViewController <DownloadDelegate>
@end

@implementation ViewController
- (void)startDownload {
    DownloadManager *mgr = [[DownloadManager alloc] init];
    mgr.delegate = self;                  // 设置自己为代理
    [mgr downloadURL:@"https://example.com/file.zip"];
}

- (void)download:(NSString *)url didProgress:(float)percent {
    self.progressView.progress = percent; // 更新进度条
}

- (void)downloadDidFinish:(NSString *)url data:(NSData *)data {
    [self saveFile:data];                 // 保存文件
}

- (void)downloadDidFail:(NSString *)url error:(NSError *)error {
    [self showAlert:error.localizedDescription];
}
@end

委托模式的三个核心要素:

  1. @protocol 定义接口:规定"代理能做什么"
  2. weak 修饰 delegate:防止循环引用(A→B, B→delegate→A)
  3. respondsToSelector: 检查:调用 @optional 方法前的安全检查

# 3.2.5 协议使用场景

协议(Protocol)具有广泛的使用场景,以下是一些常见的协议使用场景:

  1. 委托模式(Delegate Pattern):协议常用于实现委托模式,其中一个类(委托方)将某些任务委托给另一个类(委托对象)来处理。
  2. 数据源模式(Data Source Pattern):协议可以用于实现数据源模式,其中一个类(数据源)提供数据给另一个类(数据使用者)。
  3. 视图控制器之间的通信:协议可以用于视图控制器之间的通信,例如一个视图控制器需要将数据传递给另一个视图控制器。
  4. 网络请求回调:协议可以用于处理网络请求的回调,例如在网络请求完成后通知调用方。

委托 vs 数据源:

Delegate DataSource
职责 行为委托(事件响应) 数据提供
典型 UITableViewDelegate UITableViewDataSource
返回值 通常 void 返回数据
方法风格 didSelectRowAt: numberOfRows / cellForRowAt:

# 3.2.6 协议的判断

如何判断类是否实现了某个协议?要判断一个类是否实现了某个协议,可以使用conformsToProtocol:方法。这个方法可以检查一个类是否遵循了指定的协议。

// 判断类是否实现了某个协议
if ([MyClass conformsToProtocol:@protocol(MyProtocol)]) {
    NSLog(@"MyClass 实现了 MyProtocol 协议");
}

// 判断对象是否实现了某个协议
if ([myObject conformsToProtocol:@protocol(MyProtocol)]) {
    NSLog(@"myObject 实现了 MyProtocol 协议");
}

# 3.3 理解动态类型设计

# 3.3.1 理解动态类型

动态类型是指在运行时确定对象的类型,而不是在编译时确定。Objective-C是一种动态类型语言,它允许在运行时进行类型检查和类型转换。

OC 动态性的基石——Runtime:每个 OC 对象都有一个 isa 指针指向它的类对象,类对象中存储了方法列表。运行时通过 isa 在运行时查找方法——这个机制让 OC 拥有了其他静态语言不具备的灵活性。

# 3.3.2 理解id类型

id类型:id是Objective-C中的一种特殊类型,表示一个未知类型的对象。

可以将任何对象赋值给id类型的变量,而不需要进行编译时的类型检查。在运行时,可以使用动态类型检查和消息传递来操作id类型的对象。

//1.未知类型的对象:当你不确定一个对象的具体类型时,可以使用id类型来存储该对象的引用。
id unknownObject = someObject;

//2.动态类型检查
if ([unknownObject isKindOfClass:[NSString class]]) {
    // 对象是NSString类型
}

//3.动态方法调用
id someObject = [SomeClass new];
[someObject performSelector:@selector(someMethod)];

//泛型集合
NSArray<id> *genericArray = @[object1, object2, object3];

要注意的是,由于id类型是一种未知类型,编译器无法提供类型检查和自动补全等功能。因此,在使用id类型时,需要确保在运行时进行适当的类型检查,以避免潜在的类型错误。

id 和 void * 的区别?id 表示一个指向 Objective-C 对象的指针,而 void * 可以表示为任何指针。

# 3.3.3 动态类型检查

Objective-C提供了一些方法来进行动态类型检查,例如isKindOfClass:和isMemberOfClass:。

// 检查是否是某个类或其子类(遍历继承链)
if ([someObject isKindOfClass:[NSString class]]) {
    // NSString 或其子类
}

// 检查是否是某个类的确切实例(不包含子类)
if ([someObject isMemberOfClass:[NSString class]]) {
    // 精确是 NSString,不是 NSMutableString
}

// 检查是否实现了某个方法
if ([someObject respondsToSelector:@selector(someMethod)]) {
    [someObject performSelector:@selector(someMethod)];
}

// 检查是否遵循了协议
if ([someObject conformsToProtocol:@protocol(MyProtocol)]) {
    // 实现了协议
}

四个检查方法速查:

方法 检查什么 含子类/父类
isKindOfClass: 是否是指定类的实例或子类 ✅
isMemberOfClass: 是否精确是该类的实例 ❌
respondsToSelector: 是否实现了方法 ✅(继承也算)
conformsToProtocol: 是否遵循了协议 ✅

# 3.3.4 动态类型调用

1.performSelector:动态调用无参数方法。

[obj performSelector:@selector(doSomething)];

2.performSelector:withObject:动态调用带一个参数的方法。

[obj performSelector:@selector(doSomethingWith:) withObject:arg];

3.performSelector:withObject:withObject:动态调用带两个参数的方法。

[obj performSelector:@selector(doSomethingWith:with:) withObject:arg1 withObject:arg2];

4.NSInvocation,用于动态调用任意参数的方法。

SEL selector = @selector(doSomethingWith:with:);
NSMethodSignature *signature = [obj methodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:obj];
[invocation setSelector:selector];
[invocation setArgument:&arg1 atIndex:2];  // 0=self, 1=_cmd
[invocation setArgument:&arg2 atIndex:3];
[invocation invoke];

动态类型调用的注意事项

  1. 性能开销:动态类型调用比静态类型调用慢,因为需要在运行时查找方法。
  2. 安全性:动态调用可能导致运行时错误(如方法不存在),需要做好检查。
  3. 可读性:过度使用动态类型调用会降低代码的可读性和可维护性。

# 3.3.5 动态类型利弊

动态类型的特性使得Objective-C具有灵活性和可扩展性。它允许在运行时根据实际情况进行类型检查和类型转换,以适应不同的需求和场景。

但需要注意,过度使用动态类型可能会导致代码的可读性和可维护性下降,因此需要谨慎使用。

动态性的三个经典应用:

  • Method Swizzling:运行时替换方法实现(线上热修复的基础)
  • KVC/KVO:通过字符串访问和监听属性
  • JSON→Model:利用 Runtime 获取属性列表,自动映射

# 3.4 类别如何使用

# 3.4.1 理解类别机制

类别(Category)是一种机制,用于在不修改原始类的情况下扩展类的功能。类别允许开发者向现有类添加新的方法,也可以覆盖原方法(虽然不建议)。

类别 vs 继承 vs 扩展:

Category 继承 Extension(类扩展)
添加方法 ✅ ✅ ✅
添加属性 需关联对象 ✅ ✅
添加实例变量 ❌ ✅ ✅
覆盖方法 ⚠️ 编译无警告但有风险 ✅ ❌
对原始类影响 影响所有实例 不影响 影响所有实例

# 3.4.2 类别使用场景

1.扩展类的功能:类别允许在不创建子类或修改原始类的情况下,向现有类添加新的方法。这对于向系统类或第三方库的类添加自定义功能非常有用。

2.代码组织:把一个复杂类的不同功能分散到多个 .m 文件中——如 ViewController+TableView.m、ViewController+Search.m。

3.避免命名冲突:在使用类别时,需要注意避免与其他类别或原始类中的方法产生命名冲突。最好使用特定的前缀或命名约定来命名类别中的方法,以确保命名的唯一性。

# 3.4.3 类别使用案例

下面是一个简单的示例,演示了如何使用类别来扩展NSString类的功能:

// 假设有一个名为 NSString+CustomMethods 的类别文件
@interface NSString (CustomMethods)

//反转字符串
- (NSString *)reversedString;

@end

@implementation NSString (CustomMethods)

- (NSString *)reversedString {
    NSMutableString *reversedString = [NSMutableString string];
    for (NSInteger i = self.length - 1; i >= 0; i--) {
        [reversedString appendString:[NSString stringWithFormat:@"%C", [self characterAtIndex:i]]];
    }
    return reversedString;
}

@end

int main(int argc, char * argv[]) {
    @autoreleasepool {
        NSString *originalString = @"Hello, World!";
        NSString *reversedString = [originalString reversedString];
        NSLog(@"%@", reversedString); // 输出:!dlroW ,olleH
    }
    return 0;
}

# 3.4.4 类别关联对象

Category 不能直接添加属性(没有实例变量存值),但可以通过 Runtime 的"关联对象"来实现:

#import <objc/runtime.h>

@interface UIView (Badge)

@property (nonatomic, copy) NSString *badgeValue;  // Category 中声明属性

@end

@implementation UIView (Badge)

static const void *kBadgeKey = &kBadgeKey;

- (NSString *)badgeValue {
    return objc_getAssociatedObject(self, kBadgeKey);
}

- (void)setBadgeValue:(NSString *)badgeValue {
    objc_setAssociatedObject(self, kBadgeKey, badgeValue, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

@end

// 用法
view.badgeValue = @"99+";

关联策略对应 property 修饰符:

关联策略 等价修饰符
OBJC_ASSOCIATION_ASSIGN assign
OBJC_ASSOCIATION_RETAIN_NONATOMIC strong, nonatomic
OBJC_ASSOCIATION_COPY_NONATOMIC copy, nonatomic
OBJC_ASSOCIATION_RETAIN strong, atomic

# 3.5 Blocks设计

# 3.5.1 Block是什么

Blocks 是 Objective-C 中一种特殊的对象,它可以直接执行一段代码,类似其它语言中的 lambda 表达式。Block 本质上是一个携带了上下文(捕获的变量)的可执行对象。

Block 与其他语言的闭包对比:

ObjC Block Swift Closure C++ Lambda Java Lambda
语法 ^ { } { } [ ]( ){ } ( ) -> { }
捕获变量 默认只读,__block 可写 自动判断 显式 [=] / [&] 自动(effectively final)
内存管理 ARC 管理,需注意循环引用 ARC 管理 手动 GC 管理

# 3.5.2 Block使用

Block 字面量,我们可以使用 ^ 符创建一个 block 字面量:

^{
NSLog(@"This is a block");
}

类似与 C 中的方法指针,我们可以使用下面的语法去引用一个 block:

void (^simpleBlock)(void);

以上可以理解为我们创建了一个 simpleBlock 变量来表示一个参数为 void,返回值也为 void 的方法。之后,我们可以创建方法体:

simpleBlock = ^ {
  NSLog(@"This is a block");
}

当然,我们也可以将上面两个步骤合二为一:

void (^simpleBlock)(void) = ^ {
  NSLog(@"This is a block");
}

另外,block 也可以包含参数和返回值:

double (^addBlock)(double, double) = 
  ^(double firstValue, double secondValue) {
    return firstValue * secondValue;
  };

Block 作为方法参数——OC 中最常见的回调模式:

// 声明接受 Block 参数的方法
- (void)fetchDataWithCompletion:(void (^)(NSData *data, NSError *error))completion;

// 调用
[self fetchDataWithCompletion:^(NSData *data, NSError *error) {
    if (error) { NSLog(@"Error: %@", error); return; }
    self.imageView.image = [UIImage imageWithData:data];
}];

# 3.6 KVO(键值观察)

# 3.6.1 添加观察者

// 添加观察者
[person addObserver:self
         forKeyPath:@"age"
            options:NSKeyValueObservingOptionNew
            context:nil];

# 3.6.2 回调

// 回调
- (void)observeValueForKeyPath:(NSString *)keyPath 
                      ofObject:(id)object 
                        change:(NSDictionary *)change 
                       context:(void *)context {
    if ([keyPath isEqualToString:@"age"]) {
        NSLog(@"New age: %@", change[NSKeyValueChangeNewKey]);
    }
}

KVO 的四个关键注意:

  1. 移除观察者:dealloc 中必须 removeObserver,否则崩溃
  2. context 区分:多个观察者用不同的 context 指针做区分,而不是全用 nil 然后拼 keyPath 字符串对比
  3. 自动/手动触发:默认 setter 自动触发;手动触发需重写 +automaticallyNotifiesObserversForKey: 返回 NO,然后在 setter 中调 willChangeValueForKey: / didChangeValueForKey:
  4. 一个回调入口:所有 KVO 通知都进同一个 observeValueForKeyPath:——当属性多时必须做好分发

# 3.6.3 KVO实现原理

KVO 通过 Runtime 动态创建子类来实现:

添加观察者前:
  person → isa → Person (class)

添加观察者后:
  person → isa → NSKVONotifying_Person (动态子类!)
                      └→ superclass → Person

NSKVONotifying_Person 重写了被观察属性的 setter:
  - (void)setAge:(NSInteger)age {
      [self willChangeValueForKey:@"age"];   // 通知旧值
      [super setAge:age];                    // 实际赋值
      [self didChangeValueForKey:@"age"];    // 通知新值→触发 observeValueForKeyPath
  }

性能影响:KVO 的代价是一次 isa 指针替换 + setter 前后两个额外方法调用。极高频的属性变化(如每秒数千次)不应使用 KVO。

# 3.7 多线程使用

# 3.7.1 线程基本概念

  • 线程:线程是程序执行的最小单位,一个进程可以包含多个线程。
  • 主线程:也称为 UI 线程,负责更新 UI 和处理用户交互。
  • 后台线程:用于执行耗时操作,避免阻塞主线程。

OC 三种多线程方案的层次关系:

GCD (C API, 底层)          ← 性能最高,最灵活
  ↑
NSOperation (OC 封装)      ← 依赖/取消/并发控制
  ↑
NSThread (最原始)          ← 直接管理线程生命周期

# 3.7.2 使用NSThread

NSThread 是 Objective-C 中最基础的线程管理类,可以显式创建和管理线程。

1.创建线程

// 方式 1:使用类方法
[NSThread detachNewThreadSelector:@selector(threadMethod:) toTarget:self withObject:nil];

// 方式 2:使用实例方法
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadMethod:) object:nil];
[thread start];

2.线程方法

- (void)threadMethod:(id)object {
    NSLog(@"Thread is running");
    // 执行耗时操作
    [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
    NSLog(@"Thread finished");
}

3.线程同步

使用 @synchronized 实现线程同步。

@synchronized(self) {
    // 线程安全的代码块
}

# 3.7.3 NSOperation

NSOperation 和 NSOperationQueue 是基于 GCD 的高级抽象,提供了更强大的任务管理功能。

1.创建 NSOperation

NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"Operation is running");
    [NSThread sleepForTimeInterval:2];
    NSLog(@"Operation finished");
}];

2.创建 NSOperationQueue

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 3; // 设置最大并发数
[queue addOperation:operation];

3.依赖关系

可以设置 NSOperation 之间的依赖关系。

NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"Operation 1");
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"Operation 2");
}];
[operation2 addDependency:operation1]; // operation2 依赖于 operation1
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation1];
[queue addOperation:operation2];

NSOperationQueue vs GCD 选择:

NSOperationQueue GCD
依赖关系 ✅ addDependency ❌ 需手动实现
取消任务 ✅ cancel ❌ 需手动实现
最大并发数 ✅ maxConcurrentOperationCount ❌ 用信号量模拟
代码复杂度 高(每个任务一个对象) 低(Block 即任务)

# 3.7.4 GCD多线程

Grand Central Dispatch (GCD),GCD 是 Apple 提供的多线程编程解决方案,基于 C 语言 API,性能高效且易于使用。

1.创建队列

  • 主队列:串行队列,用于更新 UI。
  • 全局队列:并发队列,用于执行后台任务。
  • 自定义队列:可以是串行或并发队列。
// 主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();

// 全局队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

// 自定义队列
dispatch_queue_t customQueue = dispatch_queue_create("com.example.customQueue", DISPATCH_QUEUE_SERIAL);

2.执行任务

// 异步执行
dispatch_async(globalQueue, ^{
    // 耗时操作
    [NSThread sleepForTimeInterval:2];

    // 回到主线程更新 UI
    dispatch_async(mainQueue, ^{
        NSLog(@"Update UI");
    });
});

// 同步执行(会阻塞当前线程)
dispatch_sync(customQueue, ^{
    NSLog(@"Sync task is running");
});

3.延迟执行

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    NSLog(@"Delayed task");
});

4.任务组

使用 dispatch_group 管理一组任务。

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_group_async(group, queue, ^{ NSLog(@"Task 1"); });
dispatch_group_async(group, queue, ^{ NSLog(@"Task 2"); });

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"All tasks finished");
});

# 3.7.5 线程安全

1.使用 @synchronized

@synchronized(self) {
    // 线程安全的代码块
}

2.使用 NSLock

NSLock *lock = [[NSLock alloc] init];
[lock lock];
// 线程安全的代码块
[lock unlock];

3.使用 dispatch_semaphore

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// 线程安全的代码块
dispatch_semaphore_signal(semaphore);

# 3.8 Block 内存管理深入

# 3.8.1 Block 的三种类型

Block 在内存中分三种类型,了解它们的区别是避免崩溃的关键:

类型 存储区域 何时产生 是否需要 copy
__NSGlobalBlock__ 全局区 不捕获任何外部变量 否
__NSStackBlock__ 栈区 捕获外部变量,未 copy 生命周期限于作用域
__NSMallocBlock__ 堆区 StackBlock 执行 copy 后 可安全传递

关键规则:需要把 Block 作为属性、回调保存时,必须使用 copy 修饰符,将 StackBlock 拷贝到堆上。ARC 下系统自动处理了大部分场景,但理解原理有助于排查疑难 bug。

@property (nonatomic, copy) void (^completionBlock)(void); // ✅ 必须用 copy

# 3.8.2 Block 修改外部变量

Block 默认捕获外部变量为只读副本。如需在 Block 内部修改外部变量,使用 __block 修饰符:

__block int count = 0;
void (^increment)(void) = ^{
    count++; // ✅ __block 允许修改
};
increment();
NSLog(@"%d", count); // 输出 1

__block 的工作原理:编译器将变量从栈上移到堆上,Block 和外部作用域共享堆上的同一份变量。

# 3.8.3 Block 循环引用的防御清单

每次写 Block 时自问三问:① Block 内部有没有用 self.xxx 或 [self method]?② Block 会不会被 self 或其子对象长期持有(属性、数组、字典)?③ 如果会,是否使用了 weak-strong dance?

常见高危场景:网络回调、定时器回调、通知监听 Block、动画 completion。

# 3.9 GCD 实战模式总结

# 3.9.1 串行队列做锁

使用串行队列替代 @synchronized 或 NSLock,性能更好且避免死锁风险。

@property (nonatomic, strong) dispatch_queue_t syncQueue;
// 初始化
_syncQueue = dispatch_queue_create("com.app.sync", DISPATCH_QUEUE_SERIAL);

// 读操作
- (id)objectForKey:(NSString *)key {
    __block id obj;
    dispatch_sync(_syncQueue, ^{ obj = _dict[key]; });
    return obj;
}

// 写操作:async + barrier 保证写入时独占
- (void)setObject:(id)obj forKey:(NSString *)key {
    dispatch_barrier_async(_syncQueue, ^{ _dict[key] = obj; });
}

# 3.9.2 dispatch_once 实现单例

+ (instancetype)sharedInstance {
    static MyManager *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}

dispatch_once 是线程安全且最高效的单例实现方式,比 @synchronized 快一个数量级。

# 3.10 综合案例:网络图片下载器

用一个完整的案例串联本章知识点——枚举、协议、Block、GCD:

// ── DownloadState.h (枚举) ──
typedef NS_ENUM(NSInteger, DownloadState) {
    DownloadStateIdle = 0,
    DownloadStateDownloading,
    DownloadStateFinished,
    DownloadStateFailed
};

// ── ImageDownloader.h ──
@class ImageDownloader;
@protocol ImageDownloaderDelegate <NSObject>    // 协议
@optional
- (void)imageDownloader:(ImageDownloader *)downloader didUpdateState:(DownloadState)state;
@end

typedef void (^ImageDownloadCompletion)(UIImage *image, NSError *error);  // Block 类型

@interface ImageDownloader : NSObject

@property (nonatomic, weak) id<ImageDownloaderDelegate> delegate;  // 委托(弱引用)
@property (nonatomic, readonly) DownloadState state;               // 只读状态

- (void)downloadImageFromURL:(NSString *)url
                  completion:(ImageDownloadCompletion)completion;  // Block 回调

@end

// ── ImageDownloader.m ──
@interface ImageDownloader ()
@property (nonatomic, assign) DownloadState state;
@end

@implementation ImageDownloader

- (void)downloadImageFromURL:(NSString *)url
                  completion:(ImageDownloadCompletion)completion {

    // 更新状态 → 通知 delegate
    self.state = DownloadStateDownloading;
    if ([self.delegate respondsToSelector:@selector(imageDownloader:didUpdateState:)]) {
        [self.delegate imageDownloader:self didUpdateState:self.state];
    }

    // GCD 异步下载
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]];
        if (!data) {
            self.state = DownloadStateFailed;
            dispatch_async(dispatch_get_main_queue(), ^{
                completion(nil, [NSError errorWithDomain:@"download" code:-1 userInfo:nil]);
            });
            return;
        }

        UIImage *image = [UIImage imageWithData:data];
        self.state = DownloadStateFinished;

        // 回到主线程回调
        dispatch_async(dispatch_get_main_queue(), ^{
            completion(image, nil);
            if ([self.delegate respondsToSelector:@selector(imageDownloader:didUpdateState:)]) {
                [self.delegate imageDownloader:self didUpdateState:self.state];
            }
        });
    });
}

@end

// ── 使用方 ──
@interface ViewController () <ImageDownloaderDelegate>
@property (nonatomic, strong) ImageDownloader *downloader;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.downloader = [[ImageDownloader alloc] init];
    self.downloader.delegate = self;

    // Block + weak-strong dance 防止循环引用
    __weak typeof(self) weakSelf = self;
    [self.downloader downloadImageFromURL:@"https://example.com/photo.jpg"
                               completion:^(UIImage *image, NSError *error) {
        __strong typeof(weakSelf) strongSelf = weakSelf;
        if (error) {
            [strongSelf showError:error];
        } else {
            strongSelf.imageView.image = image;
        }
    }];
}

// 协议方法:监听下载状态
- (void)imageDownloader:(ImageDownloader *)downloader didUpdateState:(DownloadState)state {
    switch (state) {
        case DownloadStateDownloading: [self showLoading]; break;
        case DownloadStateFinished:    [self hideLoading]; break;
        case DownloadStateFailed:      [self showRetry];   break;
        default: break;
    }
}

- (void)dealloc {
    // 清理
}
@end

这个案例用到的知识点:

  • NS_ENUM 定义下载状态枚举
  • @protocol + weak delegate 实现状态通知(委托模式)
  • dispatch_async 异步后台下载 + 主线程回调(GCD)
  • Block 作为 completion handler + __weak/__strong 防循环引用
  • respondsToSelector: 安全调用 @optional 方法
  • @property (readonly) 对外只读,类扩展中重新声明为可写
上次更新: 2026/07/05, 14:43:27
面向对象编程
ObjC内存管理

← 面向对象编程 ObjC内存管理→

最近更新
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号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式