ObjC高级语法
# 03.ObjC高级语法
# 目录介绍
- 3.1 枚举的设计和使用
- 3.2 协议与委托
- 3.3 理解动态类型设计
- 3.4 类别如何使用
- 3.5 Blocks设计
- 3.6 KVO(键值观察)
- 3.7 多线程使用
- 3.8 Block 内存管理深入
- 3.9 GCD 实战模式总结
- 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
- @required修饰的方法的声明,就必须实现,编译器会警告,默认属性
- @optional修饰的方法的声明,可以实现或者不实现,不实现编译器也不会警告
为什么委托常用 weak? 防止循环引用——A 持有 B,B 的 delegate 又指回 A。如果用 strong,两者永远不释放。
@property (nonatomic, weak) id<MyProtocol> delegate; // ✅ 弱引用
# 3.2.3 协议使用步骤
在实践中,可以按照以下步骤使用协议:
- 定义协议:使用@protocol关键字定义协议,并在其中声明需要的方法、属性等。
- 遵循协议:在类的声明中使用
来遵循特定的协议。 - 实现协议方法:在类的实现中,根据协议的要求实现相应的方法。
- 使用协议:通过协议类型的指针或引用,可以调用协议中定义的方法,而不需要关心具体的类。
# 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
委托模式的三个核心要素:
- @protocol 定义接口:规定"代理能做什么"
weak修饰 delegate:防止循环引用(A→B, B→delegate→A)respondsToSelector:检查:调用 @optional 方法前的安全检查
# 3.2.5 协议使用场景
协议(Protocol)具有广泛的使用场景,以下是一些常见的协议使用场景:
- 委托模式(Delegate Pattern):协议常用于实现委托模式,其中一个类(委托方)将某些任务委托给另一个类(委托对象)来处理。
- 数据源模式(Data Source Pattern):协议可以用于实现数据源模式,其中一个类(数据源)提供数据给另一个类(数据使用者)。
- 视图控制器之间的通信:协议可以用于视图控制器之间的通信,例如一个视图控制器需要将数据传递给另一个视图控制器。
- 网络请求回调:协议可以用于处理网络请求的回调,例如在网络请求完成后通知调用方。
委托 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];
动态类型调用的注意事项
- 性能开销:动态类型调用比静态类型调用慢,因为需要在运行时查找方法。
- 安全性:动态调用可能导致运行时错误(如方法不存在),需要做好检查。
- 可读性:过度使用动态类型调用会降低代码的可读性和可维护性。
# 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 的四个关键注意:
- 移除观察者:
dealloc中必须removeObserver,否则崩溃 - context 区分:多个观察者用不同的
context指针做区分,而不是全用nil然后拼keyPath字符串对比 - 自动/手动触发:默认 setter 自动触发;手动触发需重写
+automaticallyNotifiesObserversForKey:返回 NO,然后在 setter 中调willChangeValueForKey:/didChangeValueForKey: - 一个回调入口:所有 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)对外只读,类扩展中重新声明为可写