使用 NSArray、块和手动引用计数导致崩溃所需的说明
Clarifications needed for a crash using NSArray, blocks and Manual Reference Counting
我需要澄清一下我在使用 NSArray
、块和手动引用计数时遇到的崩溃。我的目标是将块存储在一个集合中(NSArray
在这种情况下)以便将来重用它们。
我设置了一个小样本来重现这个问题。特别是,我有一个如下所示的 class 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(请注意,我已经启用了 所有异常断点)。特别是,应用程序停止在 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 的评论重述我的想法。
默认情况下,如果未向其发送 copy
消息(ARC 为我们完成此操作),则会在堆栈上创建一个块,以便将其移动到堆上。因此,这种情况下的情况如下。我创建了一个 autorelease
数组并添加了两个块,其中 retain
以隐式方式被调用。当 applicationDidFinishLaunching
方法完成执行时,块,因为在堆栈上创建(它们是 automatic
变量)消失。稍后,名为 blocks
的数组将被释放,因为它已被标记为 autorelease
。因此,它会崩溃,因为它会将 release
对象发送到不再存在的块。
所以,我的问题如下:将 retain
消息发送到堆栈上的块是什么意思?为什么数组是崩溃的根源(见回溯)? 换句话说,既然一个块在堆栈上,它会增加它的保留计数吗?什么时候超出范围?上瘾了,为什么如果我注释 [weakSelf doSomething1]
行代码可以正常工作?这部分我不是很清楚。
您正在将堆栈中的对象粘贴到自动释放数组中。 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);
}
}
Compiled/run as -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 段的只读可执行页面),因此,没有要释放的分配并且 retain/release/autorelease 是空操作。
如您所见,静态块在 0x67048 左右结束(这个数字可能会从 运行 运行 变化,顺便说一句,由于各种原因。内存不足。
事实上,由于有 sleep(),我们可以 运行 vmmap 对 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 段中。请注意,这并不意味着代码位于该可写区域(如果是,则为安全漏洞)。该代码位于映射到可读区域的 TEXT 段中。
我需要澄清一下我在使用 NSArray
、块和手动引用计数时遇到的崩溃。我的目标是将块存储在一个集合中(NSArray
在这种情况下)以便将来重用它们。
我设置了一个小样本来重现这个问题。特别是,我有一个如下所示的 class 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(请注意,我已经启用了 所有异常断点)。特别是,应用程序停止在 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 的评论重述我的想法。
默认情况下,如果未向其发送 copy
消息(ARC 为我们完成此操作),则会在堆栈上创建一个块,以便将其移动到堆上。因此,这种情况下的情况如下。我创建了一个 autorelease
数组并添加了两个块,其中 retain
以隐式方式被调用。当 applicationDidFinishLaunching
方法完成执行时,块,因为在堆栈上创建(它们是 automatic
变量)消失。稍后,名为 blocks
的数组将被释放,因为它已被标记为 autorelease
。因此,它会崩溃,因为它会将 release
对象发送到不再存在的块。
所以,我的问题如下:将 retain
消息发送到堆栈上的块是什么意思?为什么数组是崩溃的根源(见回溯)? 换句话说,既然一个块在堆栈上,它会增加它的保留计数吗?什么时候超出范围?上瘾了,为什么如果我注释 [weakSelf doSomething1]
行代码可以正常工作?这部分我不是很清楚。
您正在将堆栈中的对象粘贴到自动释放数组中。 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);
}
}
Compiled/run as -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 段的只读可执行页面),因此,没有要释放的分配并且 retain/release/autorelease 是空操作。
如您所见,静态块在 0x67048 左右结束(这个数字可能会从 运行 运行 变化,顺便说一句,由于各种原因。内存不足。
事实上,由于有 sleep(),我们可以 运行 vmmap 对 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 段中。请注意,这并不意味着代码位于该可写区域(如果是,则为安全漏洞)。该代码位于映射到可读区域的 TEXT 段中。