【发布时间】:2015-02-16 20:03:37
【问题描述】:
我需要对使用NSArray、块和手动引用计数时遇到的崩溃进行一些说明。我的目标是将块存储在集合中(在这种情况下为NSArray),以便将来重用它们。
我设置了一个小样本来复制该问题。特别是,我有一个类Item,如下所示:
#import <Foundation/Foundation.h>
typedef void(^MyBlock)();
@interface Item : NSObject
- (instancetype)initWithBlocks:(NSArray*)blocks;
@end
#import "Item.h"
@interface Item ()
@property (nonatomic, strong) NSArray *blocks;
@end
@implementation Item
- (instancetype)initWithBlocks:(NSArray*)blocks
{
self = [super init];
if (self) {
NSMutableArray *temp = [NSMutableArray array];
for (MyBlock block in blocks) {
[temp addObject:[[block copy] autorelease]];
}
_blocks = [temp copy];
}
return self;
}
用法如下(我在应用委托中使用)。
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
__block typeof(self) weakSelf = self;
MyBlock myBlock1 = ^() {
[weakSelf doSomething1];
};
MyBlock myBlock2 = ^() {
[weakSelf doSomething1];
};
NSArray *blocks = @[myBlock1, myBlock2];
// As MartinR suggested the code crashes even
// if the following line is commented
Item *item = [[Item alloc] initWithBlocks:blocks];
}
如果我运行该应用程序,它会因 EXC_BAD_INSTRUCTION 而崩溃(请注意,我已经启用了 All Exceptions 断点)。特别是,应用程序在 main 中停止。
int main(int argc, const char * argv[]) {
return NSApplicationMain(argc, argv);
}
注意:根据 Ken Thomases 的建议,如果您在 llvm 控制台上使用 bt 命令,您将看到回溯。在这种情况下,它显示以下内容:
-[__NSArrayI dealloc]
如果我评论 [weakSelf doSomethingX]; 它不会崩溃(这并不意味着这是正确的)。
将代码稍微修改如下,一切正常。
// Item does not do anymore the copy/autorelease dance
// since used in the declaration of the blocks
- (instancetype)initWithBlocks:(NSArray*)blocks
{
self = [super init];
if (self) {
_blocks = [blocks retain];
}
return self;
}
和
__block typeof(self) weakSelf = self;
MyBlock myBlock1 = [[^() {
[weakSelf doSomething1];
} copy] autorelease];
MyBlock myBlock2 = [[^() {
[weakSelf doSomething1];
} copy] autorelease];
NSArray *blocks = @[myBlock1, myBlock2];
Item *item = [[Item alloc] initWithBlocks:blocks];
这里有什么意义?我想我错过了什么,但我不知道是什么。
更新 1
好的。我将尝试根据 @Martin R 和 @Ken Thomases 的 cmets 回顾我的想法。
默认情况下,如果没有向它发送copy 消息(ARC 为我们这样做),则在堆栈上创建一个块,以便将它移动到堆上。因此,这种情况下的情况如下。我创建了一个autorelease 数组并添加了两个块,其中retain 以隐式方式被调用。当applicationDidFinishLaunching 方法完成执行时,在堆栈上创建的块(它们是automatic 变量)消失。稍后,名为blocks 的数组将被释放,因为它已被标记为autorelease。因此,它会崩溃,因为它会将release 对象发送到不再存在的块。
所以,我的问题如下:向堆栈上的块发送retain 消息是什么意思?为什么数组是崩溃的根源(见回溯)? 换句话说,既然一个块在堆栈上,它会不会增加它的保留计数?当它超出范围时?上瘾,为什么如果我评论 [weakSelf doSomething1] 行代码可以正常工作?这部分我不是很清楚。
【问题讨论】:
-
我认为问题出在
NSArray *blocks = @[myBlock1, myBlock2];。与您的 Item 类一样,添加到集合时必须复制块:NSArray *blocks = @[[[myBlock1 copy] autorelease], [[myBlock2 copy] autorelease]]; -
Xcode 可能正在折叠堆栈跟踪。确保将堆栈跟踪下方的滑块一直拖到右侧。此外,当 Xcode 让您的程序在崩溃时停止时,您可以在控制台视图的“lldb”提示符处键入“bt”。
-
马丁可能是对的。数组字面量
@[ ... ]有效地创建了一个自动释放的数组。崩溃可能发生在最终释放时,即在定义块的范围之后,因此在它们被释放之后。-release在那个时候被发送到垃圾对象。您可以尝试使用+alloc和-initWithObjects:创建该数组,然后显式释放它,但这并不能保证它不会存在于自动释放池中。 -
即使省略
Item *item = ...语句也会崩溃,因此问题与Item 类无关。回溯显示-[__NSArrayI dealloc],这可能是blocksarray 的释放(正如Ken 在最后一条评论中所怀疑的那样)。 -
我怀疑
-retain和-release或仍然在堆栈上的块上没有操作,因为它们不可能影响对象的实际生命周期。类似于将它们发送到具有静态生命周期的字符串文字 (@"...")。
标签: objective-c memory-management objective-c-blocks