我需要对使用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;
}
用法如下所述(我在app委托中使用)。
- (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 而崩溃(请注意,我已启用所有异常断点)。特别是,应用程序停在主要部分。
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的评论来回顾我的想法。
默认情况下,如果未向其发送copy
消息(ARC为我们执行此操作),则会在堆栈上创建一个块,以便在堆上移动它。因此,本案的情况如下。我创建了一个autorelease
数组,并添加了两个块,其中以隐式方式调用retain
。当applicationDidFinishLaunching
方法完成执行时,自从在堆栈上创建的块(它们是automatic
个变量)就会消失。稍后,名为blocks
的数组将被标记为autorelease
,因此将被释放。因此,它会崩溃,因为它会将release
对象发送到不再存在的块。
所以,我的问题如下:将retain
消息发送到堆栈中的块是什么意思?为什么数组是崩溃的来源(参见后面的跟踪)?换句话说,由于一个块在堆栈上,它是否会破坏它的保留计数?当它超出范围?瘾,为什么我评论[weakSelf doSomething1]
行代码没有问题?我不太清楚这一部分。
答案 0 :(得分:3)
您正在将堆栈中的对象粘贴到自动释放的数组中。 BOOM 随之而来。
考虑:
typedef void(^MyBlock)();
int main(int argc, char *argv[]) {
@autoreleasepool {
NSObject *o = [NSObject new];
MyBlock myBlock1 = ^() {
[o doSomething1];
};
NSLog(@"o %p", o);
NSLog(@"b %p", myBlock1);
NSLog(@"b retain %p", [myBlock1 retain]);
NSLog(@"b copy %p", [myBlock1 copy]);
NSLog(@"s %p", ^{});
sleep(1000000);
}
}
编译/运行为-i386(因为#s更小,更明显):
a.out[11729:555819] o 0x7b6510f0
a.out[11729:555819] b 0xbff2dc30
a.out[11729:555819] b retain 0xbff2dc30
a.out[11729:555819] b copy 0x7b6511a0
a.out[11748:572916] s 0x67048
由于对象位于0x7b,我们可以假设它是堆。 0xb实际上是高内存,因此是堆栈。
retain
不会导致copy
(因为这样做会导致泄密)并且基于堆栈的对象上的retain
毫无意义。
如果您将[o doSomething1];
更改为[nil doSomething1];
,那么它将变为静态块并且位于只读映射的内存中(来自mach-o的TEXT段的只读可执行页面),因此,没有分配解除分配和保留/释放/自动释放是无操作。
正如你所看到的,静态块最终在0x67048左右(由于各种原因,这个数字可能会在运行之间发生变化,btw。内存不足。
事实上,由于sleep(),我们可以针对a.out进程运行vmmap并查看:
==== Writable regions for process 11772
REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL
__DATA 00067000-00068000 [ 4K] rw-/rwx SM=ZER /tmp/a.out
也就是说,静态块位于mach-o文件的映射可写区域的第一个4K段中。请注意,这并不意味着代码位于可写区域(如果是,则为SECURITY HOLE)。代码位于TEXT段中,映射到可读区域。