名称: Objective-C Blocks and GCD 用户可调用: false 描述: 在使用Objective-C中的块(闭包)和Grand Central Dispatch进行并发编程时使用,包括块语法、捕获语义、调度队列、调度组和线程安全异步代码的模式。 允许工具: []
Objective-C Blocks and GCD
介绍
块是Objective-C的闭包实现,提供匿名函数,可以捕获周围的上下文。Grand Central Dispatch (GCD) 是苹果的低级API,用于管理并发操作,使用调度队列而不是直接线程。
块支持函数式编程模式、回调和简洁的异步API设计。GCD通过将线程管理抽象为队列来简化并发,这些队列自动在可用CPU核心上分配工作。它们一起构成了现代Objective-C并发编程的基础。
这个技能涵盖块语法和语义、捕获行为、GCD队列和调度函数、同步原语和安全并发代码的模式。
块语法和用法
块是一等对象,封装代码并可以从其定义范围捕获变量。
// 基本块语法
void (^simpleBlock)(void) = ^{
NSLog(@"Hello from block");
};
simpleBlock(); // 调用块
// 带参数的块
int (^addBlock)(int, int) = ^(int a, int b) {
return a + b;
};
int result = addBlock(5, 3); // 8
// 带返回类型的块
NSString *(^greetBlock)(NSString *) = ^NSString *(NSString *name) {
return [NSString stringWithFormat:@"Hello, %@", name];
};
NSString *greeting = greetBlock(@"Alice");
// 块作为方法参数
- (void)fetchDataWithCompletion:
(void (^)(NSData *data, NSError *error))completion {
dispatch_async(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 模拟网络调用
NSData *data = [@"response" dataUsingEncoding:NSUTF8StringEncoding];
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) {
completion(data, nil);
}
});
});
}
// 使用基于块的API
- (void)loadData {
[self fetchDataWithCompletion:^(NSData *data, NSError *error) {
if (error) {
NSLog(@"Error: %@", error);
} else {
NSLog(@"Data: %@", data);
}
}];
}
// 块类型的typedef
typedef void (^CompletionBlock)(BOOL success);
typedef void (^DataBlock)(NSData *data, NSError *error);
typedef NSString *(^TransformBlock)(NSString *input);
- (void)performOperationWithCompletion:(CompletionBlock)completion {
// 异步操作
dispatch_async(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 工作
BOOL success = YES;
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) {
completion(success);
}
});
});
}
// 块在集合中
NSArray *blocks = @[
^{ NSLog(@"Block 1"); },
^{ NSLog(@"Block 2"); },
^{ NSLog(@"Block 3"); }
];
for (void (^block)(void) in blocks) {
block();
}
// 块属性
@interface AsyncOperation : NSObject
@property (nonatomic, copy) CompletionBlock completion;
@property (nonatomic, copy) DataBlock dataHandler;
@end
@implementation AsyncOperation
@end
块在存储在属性或集合中时必须复制,以从堆栈移动到堆存储。
块捕获语义
块从其定义范围捕获变量,对不同存储类型和限定符有不同的行为。
// 捕获局部变量
void captureExample(void) {
NSInteger x = 10;
void (^block)(void) = ^{
NSLog(@"x = %ld", (long)x); // 捕获x的值
};
x = 20;
block(); // 打印 "x = 10"(在块创建时捕获)
}
// __block限定符用于可变捕获
void mutableCaptureExample(void) {
__block NSInteger counter = 0;
void (^incrementBlock)(void) = ^{
counter++; // 可以修改计数器
};
incrementBlock();
incrementBlock();
NSLog(@"Counter: %ld", (long)counter); // 2
}
// 在方法中捕获self
@interface Counter : NSObject
@property (nonatomic, assign) NSInteger count;
- (void)incrementAsync;
@end
@implementation Counter
- (void)incrementAsync {
// 强捕获self
dispatch_async(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
self.count++; // 强捕获self
});
}
@end
// 针对self的弱-强舞蹈
@interface ViewController : UIViewController
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation ViewController
- (void)startTimer {
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
repeats:YES
block:^(NSTimer *timer) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) return;
// 安全使用strongSelf
[strongSelf updateUI];
}];
}
- (void)updateUI {
NSLog(@"Updating UI");
}
- (void)dealloc {
[self.timer invalidate];
}
@end
// 捕获对象与基本类型
void objectCaptureExample(void) {
NSMutableString *string = [NSMutableString stringWithString:@"Hello"];
void (^block)(void) = ^{
[string appendString:@" World"]; // 可以变异对象
NSLog(@"%@", string);
};
block(); // 打印 "Hello World"
}
// 块保留循环
@interface NetworkManager : NSObject
@property (nonatomic, copy) void (^completion)(NSData *data);
@end
@implementation NetworkManager
- (void)fetchData {
__weak typeof(self) weakSelf = self;
self.completion = ^(NSData *data) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) return;
[strongSelf processData:data];
};
}
- (void)processData:(NSData *)data {
NSLog(@"Processing: %@", data);
}
@end
// 捕获__block对象
void blockObjectExample(void) {
__block NSMutableArray *array = [NSMutableArray array];
void (^addBlock)(id) = ^(id object) {
[array addObject:object]; // 可以变异和重新赋值
};
addBlock(@"Item 1");
addBlock(@"Item 2");
array = [NSMutableArray array]; // 可以重新赋值
}
使用__weak来避免捕获self时的保留循环,使用__block来允许捕获变量的变异。
调度队列
GCD使用调度队列管理并发执行,串行队列按顺序执行任务,并发队列并行执行任务。
// 主队列(串行,主线程)
dispatch_async(dispatch_get_main_queue(), ^{
// 更新UI
NSLog(@"On main thread");
});
// 全局并发队列
dispatch_queue_t highPriorityQueue = dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_queue_t defaultQueue = dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t lowPriorityQueue = dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
// 在全局队列上的异步执行
dispatch_async(defaultQueue, ^{
// 后台工作
NSLog(@"Background work");
dispatch_async(dispatch_get_main_queue(), ^{
// 在主队列上更新UI
NSLog(@"UI update");
});
});
// 自定义串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.serial", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
NSLog(@"Task 1");
});
dispatch_async(serialQueue, ^{
NSLog(@"Task 2");
});
// 自定义并发队列
dispatch_queue_t concurrentQueue = dispatch_queue_create(
"com.example.concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
NSLog(@"Concurrent task 1");
});
dispatch_async(concurrentQueue, ^{
NSLog(@"Concurrent task 2");
});
// 同步调度(阻塞直到完成)
__block NSString *result;
dispatch_sync(serialQueue, ^{
result = @"Computed value";
});
NSLog(@"Result: %@", result);
// 延迟执行(dispatch after)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC),
dispatch_get_main_queue(), ^{
NSLog(@"Executed after 2 seconds");
});
// 线程安全的单例(dispatch once)
+ (instancetype)sharedInstance {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
// 服务质量(iOS 8+)
dispatch_queue_t userInitiatedQueue = dispatch_get_global_queue(
QOS_CLASS_USER_INITIATED, 0);
dispatch_queue_t utilityQueue = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0);
dispatch_async(userInitiatedQueue, ^{
// 用户发起的工作(高优先级)
});
使用主队列进行UI更新,全局队列进行后台工作,自定义队列进行同步和有序执行。
调度组
调度组协调多个异步操作,在所有任务完成时通知。
// 基本调度组
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_async(group, queue, ^{
NSLog(@"Task 3");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"All tasks complete");
});
// 等待组完成
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"After wait");
// 手动进入/离开
dispatch_group_t manualGroup = dispatch_group_create();
dispatch_group_enter(manualGroup);
[self fetchDataWithCompletion:^(NSData *data, NSError *error) {
NSLog(@"Data fetched");
dispatch_group_leave(manualGroup);
}];
dispatch_group_enter(manualGroup);
[self fetchImageWithCompletion:^(UIImage *image, NSError *error) {
NSLog(@"Image fetched");
dispatch_group_leave(manualGroup);
}];
dispatch_group_notify(manualGroup, dispatch_get_main_queue(), ^{
NSLog(@"All fetches complete");
});
// 实际示例:加载多个资源
- (void)loadAllResources {
dispatch_group_t resourceGroup = dispatch_group_create();
__block NSData *userData = nil;
__block NSData *settingsData = nil;
__block UIImage *profileImage = nil;
dispatch_group_enter(resourceGroup);
[self fetchUserDataWithCompletion:^(NSData *data) {
userData = data;
dispatch_group_leave(resourceGroup);
}];
dispatch_group_enter(resourceGroup);
[self fetchSettingsWithCompletion:^(NSData *data) {
settingsData = data;
dispatch_group_leave(resourceGroup);
}];
dispatch_group_enter(resourceGroup);
[self fetchProfileImageWithCompletion:^(UIImage *image) {
profileImage = image;
dispatch_group_leave(resourceGroup);
}];
dispatch_group_notify(resourceGroup, dispatch_get_main_queue(), ^{
// 所有资源已加载
[self updateUIWithUser:userData settings:settingsData image:profileImage];
});
}
- (void)fetchUserDataWithCompletion:(void (^)(NSData *))completion {
dispatch_async(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (completion) completion([NSData data]);
});
}
- (void)fetchSettingsWithCompletion:(void (^)(NSData *))completion {
dispatch_async(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (completion) completion([NSData data]);
});
}
- (void)fetchProfileImageWithCompletion:(void (^)(UIImage *))completion {
dispatch_async(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (completion) completion([[UIImage alloc] init]);
});
}
- (void)updateUIWithUser:(NSData *)user settings:(NSData *)settings
image:(UIImage *)image {
NSLog(@"Updating UI with all resources");
}
调度组对于协调多个异步操作并确保所有操作在继续之前完成至关重要。
调度屏障和同步
屏障在并发队列中提供对共享资源的同步访问。
// 调度屏障用于读写模式
@interface ThreadSafeCache : NSObject
@property (nonatomic, strong) dispatch_queue_t concurrentQueue;
@property (nonatomic, strong) NSMutableDictionary *cache;
@end
@implementation ThreadSafeCache
- (instancetype)init {
self = [super init];
if (self) {
self.concurrentQueue = dispatch_queue_create(
"com.example.cache",
DISPATCH_QUEUE_CONCURRENT
);
self.cache = [NSMutableDictionary dictionary];
}
return self;
}
// 允许多个读取者
- (id)objectForKey:(NSString *)key {
__block id object;
dispatch_sync(self.concurrentQueue, ^{
object = self.cache[key];
});
return object;
}
// 使用屏障进行独占写入
- (void)setObject:(id)object forKey:(NSString *)key {
dispatch_barrier_async(self.concurrentQueue, ^{
self.cache[key] = object;
});
}
// 同步屏障写入
- (void)setObjectSync:(id)object forKey:(NSString *)key {
dispatch_barrier_sync(self.concurrentQueue, ^{
self.cache[key] = object;
});
}
@end
// 使用信号量限制并发
- (void)downloadImagesWithLimit:(NSArray<NSURL *> *)urls {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
// 最大3个并发
dispatch_queue_t queue = dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (NSURL *url in urls) {
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// 下载图像
NSLog(@"Downloading: %@", url);
[NSThread sleepForTimeInterval:1.0]; // 模拟下载
dispatch_semaphore_signal(semaphore);
});
}
}
// 使用dispatch apply进行并行循环
- (void)processItems:(NSArray *)items {
dispatch_apply(items.count, dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {
id item = items[index];
NSLog(@"Processing item %zu: %@", index, item);
// 并行处理项目
});
}
// 使用dispatch_sync的互斥锁替代方案
@interface Counter2 : NSObject
@property (nonatomic, strong) dispatch_queue_t syncQueue;
@property (nonatomic, assign) NSInteger count;
@end
@implementation Counter2
- (instancetype)init {
self = [super init];
if (self) {
self.syncQueue = dispatch_queue_create("com.example.counter", DISPATCH_QUEUE_SERIAL);
self.count = 0;
}
return self;
}
- (void)increment {
dispatch_sync(self.syncQueue, ^{
self.count++;
});
}
- (NSInteger)currentCount {
__block NSInteger value;
dispatch_sync(self.syncQueue, ^{
value = self.count;
});
return value;
}
@end
屏障确保独占写入访问,同时允许并发读取,非常适合线程安全的缓存和数据结构。
基于块的API
现代Cocoa API广泛使用块进行回调,提供比委托模式更简洁的替代方案。
// 使用块的NSURLSession
- (void)fetchURL:(NSURL *)url {
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithURL:url
completionHandler:^(NSData *data, NSURLResponse *response,
NSError *error) {
if (error) {
NSLog(@"Error: %@", error);
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
// 在主线程上处理数据
NSLog(@"Data received: %@", data);
});
}];
[task resume];
}
// 使用块的UIView动画
- (void)animateView:(UIView *)view {
[UIView animateWithDuration:0.3
animations:^{
view.alpha = 0.0;
view.transform = CGAffineTransformMakeScale(0.5, 0.5);
} completion:^(BOOL finished) {
if (finished) {
[view removeFromSuperview];
}
}];
}
// 使用块的NSNotificationCenter
- (void)observeNotifications {
id observer = [[NSNotificationCenter defaultCenter]
addObserverForName:UIApplicationDidEnterBackgroundNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note) {
NSLog(@"App entered background");
}];
// 存储观察者以供稍后移除
}
// 自定义基于块的API
typedef void (^ProgressBlock)(CGFloat progress);
typedef void (^CompletionBlock2)(BOOL success, NSError *error);
@interface Downloader : NSObject
- (void)downloadFile:(NSURL *)url
progress:(ProgressBlock)progress
completion:(CompletionBlock2)completion;
@end
@implementation Downloader
- (void)downloadFile:(NSURL *)url
progress:(ProgressBlock)progress
completion:(CompletionBlock2)completion {
dispatch_async(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 模拟带进度的下载
for (NSInteger i = 0; i <= 100; i += 10) {
[NSThread sleepForTimeInterval:0.1];
dispatch_async(dispatch_get_main_queue(), ^{
if (progress) {
progress(i / 100.0);
}
});
}
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) {
completion(YES, nil);
}
});
});
}
@end
// 使用自定义块API
- (void)downloadExample {
Downloader *downloader = [[Downloader alloc] init];
NSURL *url = [NSURL URLWithString:@"https://example.com/file.zip"];
[downloader downloadFile:url
progress:^(CGFloat progress) {
NSLog(@"Progress: %.0f%%", progress * 100);
} completion:^(BOOL success, NSError *error) {
if (success) {
NSLog(@"Download complete");
} else {
NSLog(@"Download failed: %@", error);
}
}];
}
基于块的API提供内联回调处理,无需委托或通知观察者的模板代码。
最佳实践
-
在存储在属性中时复制块,将它们从堆栈移动到堆,防止悬空指针导致的崩溃
-
针对self捕获使用弱-强舞蹈,在作为属性存储的块中打破保留循环
-
将UI更新分派到主队列,使用dispatch_async确保线程安全的UI修改
-
优先使用调度组而不是嵌套回调,干净地协调多个异步操作
-
使用调度屏障进行读写模式,允许并发读取,同时确保独占写入
-
为同步创建自定义队列,而不是使用全局队列,以避免争用和优先级问题
-
在调用块之前检查nil,防止因未实现的可选块参数导致的崩溃
-
使用dispatch_once进行线程安全的单例,确保无需锁的一次性初始化
-
使用信号量限制并发,当访问网络连接等速率限制资源时
-
使用Instruments进行性能分析,识别队列争用、线程爆炸和性能瓶颈
常见陷阱
-
在作为属性存储的块中强捕获self导致保留循环,造成内存泄漏
-
在存储块时不复制块,导致堆栈分配块超出范围时崩溃
-
在当前队列上使用dispatch_sync,导致死锁;切勿同步分派到您所在的队列
-
忘记将UI更新分派到主队列,导致崩溃或未定义行为
-
过度使用dispatch_sync,不必要地阻塞线程;优先使用异步分派以提高性能
-
不平衡dispatch_group_enter/leave,导致组通知从不触发或过早触发
-
从多个队列访问可变状态而不同步,导致竞态条件和数据损坏
-
创建太多自定义队列,浪费资源;在适当的地方重用队列
-
在全局队列上使用屏障不起作用,因为屏障需要自定义并发队列
-
在弱-强舞蹈中阻塞而不进行nil检查,如果weakSelf在执行过程中变为nil,可能导致崩溃
何时使用此技能
在构建需要异步操作、并发处理或基于回调的API的iOS、macOS、watchOS或tvOS应用程序时使用块和GCD。
应用调度队列进行后台处理、网络调用、文件I/O或任何不应阻塞主线程的操作。
使用调度组协调必须在继续之前完成的多个异步操作,例如加载多个资源。
利用调度屏障实现支持并发读取和独占写入的线程安全数据结构。
在设计现代Objective-C接口时使用基于块的API,提供内联回调处理而无需委托模板。