使用NSArray,块和手动引用计数进行崩溃所需的说明

时间:2015-02-16 20:03:37

标签: objective-c memory-management objective-c-blocks

我需要对使用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]行代码没有问题?我不太清楚这一部分。

1 个答案:

答案 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段中,映射到可读区域。