面试题答案
一键面试1. Block的内存特性
- 栈上(Stack):默认情况下,Block定义在方法内部时,它会在栈上分配内存。栈上的Block生命周期与它所在的函数栈帧相同,当函数返回时,栈上的Block会被销毁。例如:
void someFunction() {
int value = 10;
void (^stackBlock)() = ^{
NSLog(@"Value: %d", value);
};
stackBlock();
}
这里的stackBlock
就是在栈上的。
- 堆上(Heap):当Block被
copy
操作时,它会从栈上复制到堆上。堆上的Block有自己独立的内存空间,其生命周期由引用计数管理。在ARC(自动引用计数)环境下,编译器会自动处理copy
操作。例如,将Block作为返回值或者存储在对象的属性中时,通常会进行copy
操作到堆上。
@property (nonatomic, copy) void (^heapBlock)();
- (void)setupBlock {
int value = 20;
void (^stackBlock)() = ^{
NSLog(@"Value: %d", value);
};
self.heapBlock = stackBlock; // 这里stackBlock被copy到堆上
}
2. 内存管理
- ARC下:ARC会自动管理Block的内存。当Block被
copy
到堆上后,ARC会根据引用计数自动释放堆上的Block。例如,当持有堆上Block的对象被释放时,如果没有其他强引用指向该Block,ARC会自动释放它。 - MRC下:在手动引用计数(MRC)环境中,需要开发者手动进行
copy
和release
操作。对栈上的Block调用copy
,将其复制到堆上并增加引用计数;当不再需要堆上的Block时,调用release
减少引用计数,当引用计数为0时,堆上的Block会被释放。
3. 多线程编程场景下的应用
- GCD(Grand Central Dispatch):GCD是iOS开发中常用的多线程编程模型,大量使用了Block。例如,在主线程以外的队列中执行任务:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
// 这里的Block会在后台队列中执行
NSLog(@"This is a background task");
});
- NSOperationQueue:NSOperationQueue也可以配合Block使用来实现多线程任务。例如:
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
// 多线程任务代码
NSLog(@"This is a task in NSOperationQueue");
}];
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
[operationQueue addOperation:blockOperation];
4. 不同内存情况对多线程编程的影响
- 栈上Block:由于栈上Block生命周期与函数栈帧相同,如果在多线程场景下使用栈上Block,当函数返回,栈上Block被销毁,可能导致多线程任务访问已释放的内存,引发程序崩溃。例如,在函数内部启动一个异步任务并使用栈上Block,函数返回后,栈上Block被销毁,而异步任务可能还未执行完,此时访问Block中的变量就会出错。
- 堆上Block:堆上的Block由于有独立的内存空间和引用计数管理,在多线程场景下相对安全。只要堆上Block的引用计数不为0,它就不会被释放,多线程任务可以正常执行。例如,在GCD中使用的Block,通常都是在堆上,这样可以确保任务在后台队列中顺利执行,不用担心内存提前释放的问题。