面试题答案
一键面试Objective-C 中 block 的实现原理
- 数据结构:
Block
本质上也是一个对象,它的结构体定义类似如下:
struct Block_descriptor { unsigned long int reserved; unsigned long int size; // 指向 copy 函数的指针 void (*copy)(void *dst, void *src); // 指向 dispose 函数的指针 void (*dispose)(void *); }; struct Block_layout { void *isa; int flags; int reserved; void (*invoke)(void *, ...); struct Block_descriptor *descriptor; // 捕获的变量 /* 如果有捕获变量,这里会包含捕获的变量 */ };
isa
指针表明block
是一个对象,invoke
是执行block
代码的函数指针,descriptor
指向描述block
属性(如大小、copy
和dispose
函数)的结构体。
- 内存管理机制:
- 栈上的 block:当
block
在函数内部定义时,它初始是在栈上的。栈上的block
生命周期与函数栈帧相同,函数结束时,栈上的block
会被销毁。 - 堆上的 block:通过
copy
操作,可以将栈上的block
复制到堆上。常见的触发block
从栈到堆复制的情况有:将block
作为函数返回值、将block
赋值给__strong
修饰的变量、将block
作为Cocoa
框架方法的参数且该方法对block
有持有语义(如GCD
相关函数)。堆上的block
需要手动管理内存,当引用计数为0时会被释放。 - 全局的 block:当
block
不捕获任何外部变量时,它会被存储在全局数据区,类似全局变量,生命周期贯穿程序始终,不需要进行额外的内存管理。
- 栈上的 block:当
block 在实际开发中的常见应用场景
- 作为回调函数:
- 示例:在网络请求中,当请求完成时,通过
block
返回结果。
typedef void(^CompletionBlock)(NSData *data, NSError *error); - (void)fetchDataWithCompletion:(CompletionBlock)completion { NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@"https://example.com"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { if (completion) { completion(data, error); } }]; [task resume]; }
- 优势:相比传统的委托模式(delegate),使用
block
代码更加紧凑,不需要额外定义协议和实现多个代理方法。可以将回调逻辑与请求逻辑写在同一处,增强了代码的可读性和维护性。
- 示例:在网络请求中,当请求完成时,通过
- 集合遍历:
- 示例:使用
block
遍历数组。
NSArray *numbers = @[@1, @2, @3]; [numbers enumerateObjectsUsingBlock:^(NSNumber * _Nonnull number, NSUInteger idx, BOOL * _Nonnull stop) { NSLog(@"%@", number); if (idx == 1) { *stop = YES; } }];
- 优势:简洁明了,直接在遍历的地方定义处理逻辑,不需要单独定义一个遍历方法。而且
block
可以方便地捕获外部变量,方便在遍历过程中进行复杂的操作。
- 示例:使用
- GCD(Grand Central Dispatch):
- 示例:在后台线程执行任务,并在主线程更新 UI。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ // 耗时操作 NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://example.com"]]; dispatch_async(dispatch_get_main_queue(), ^{ // 更新 UI UIImage *image = [UIImage imageWithData:data]; self.imageView.image = image; }); });
- 优势:
block
与GCD
紧密结合,能够方便地将任务提交到不同的队列执行。通过block
可以简洁地定义任务逻辑,并且易于管理不同任务之间的依赖关系。