在非主线程上设置一个大的 NSDictionary 属性
Setting a large NSDictionary property on a non-main thread
我有一个名为 Dictionary
的 class,其中 init
方法如下所示:
- (id) init{
self = [super init];
if (self){
[self makeEmojiDictionaries];
}
return self;
}
- (void)makeEmojiDictionaries{
//next line triggers bad_exc_access error
self.englishEmojiAllDictionary = @{@"hi" : @""}; //this is a strong, atomic property of NSDictionary
};
我的问题是实际的表情符号字典非常大,我想使用 GCD 在 非主线程 中完成所有繁重的工作。但是,每当我到达设置 self.englishEmojiAllDictionary
的行时,我总是会收到 bad_access
错误。
我正在以最正常的方式使用 GCD:
dispatch_queue_t myQueue = dispatch_queue_create("My Queue",NULL);
dispatch_async(myQueue, ^{
//Do long process activity
Dictionary *dictionary = [[Dictionary alloc] init];
});
GCD 或非主线程工作是否有我遗漏的特殊细微差别?非常感谢任何帮助 - 谢谢!
编辑 1:
如果您想自己尝试一下。我上传了一个复制此异常的 sample project。我的理论是我正在初始化的 NSDictionary
太大了。
我已将您的数据从代码移至 plist
文件,格式如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>arancia meccanica</key><string>⏰</string>
<key>uno freddo</key><string></string>
<key>un drink</key><string></string>
...
<key>bacio</key><string></string>
<key>baci</key><string></string>
</dict>
</plist>
(我拿了你的数据并使用了三次查找替换:",
=> </string>
,然后 ":@"
=> </key><string>
和 @"
=> <key>
).
然后我使用以下方法加载了数据:
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"dictionary"
ofType:@"plist"]
dictionary = [NSDictionary dictionaryWithContentsOfFile:filePath];
问题已解决。请注意,您应该永远不要将您的数据硬编码到源代码中。
错误的确切原因很难查明。 NSDictionary
文字使用方法 +[NSDictionary dictionaryWithObjects:forKeys:count:]
。
我的汇编程序知识很差,但我认为在调用这个初始化程序之前,所有的键和值都放在堆栈上。
但是,主线程的栈大小和后台线程的栈大小是有区别的(见Creating Threads in Thread Programming Guide)。
这就是为什么在后台线程上执行代码时会出现问题的原因。如果你有更多的数据,这个问题可能也会出现在主线程上。
主线程和后台线程栈大小的区别也可以通过下面简单的代码来说明:
- (void)makeEmojiDictionaries {
// allocate a lot of data on the stack
// (approximately the number of pointers we need for our dictionary keys & values)
id pointersOnStack[32500 * 2];
NSLog(@"%i", sizeof(pointersOnStack));
}
当您需要做一些缓慢的事情时,正确的模式是在后台队列中私下完成工作,然后分派回主队列以使完成的工作可供应用程序的其余部分使用。在这种情况下,您不需要创建自己的队列。您可以使用全局后台队列之一。
#import "ViewController.h"
#import "Dictionary.h"
@interface ViewController ()
@property (nonatomic, strong, readonly) Dictionary *dictionary;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self updateViews];
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
Dictionary *dictionary = [[Dictionary alloc] init];
dispatch_async(dispatch_get_main_queue(), ^{
_dictionary = dictionary;
[self updateViews];
});
});
}
- (void)updateViews {
if (self.dictionary == nil) {
// show an activity indicator or something
} else {
// show UI using self.dictionary
}
}
@end
从文件加载字典是个好主意,您可以在后台队列中执行此操作,然后将加载的字典分派回主线程。
首先,我建议你使用一个文件(plist, txt, xml, ...)来存储大数据,然后在运行时读取它,或者从远程服务器下载它。
对于你的问题,是因为堆栈大小的限制。 在 iOS 上,主线程的默认堆栈大小为 1 MB,辅助线程的默认堆栈大小为 512 KB。 您可以通过 [NSThread currentThread].stackSize
查看。
您的硬编码字典占用了将近 1 MB 的堆栈,这就是为什么您的应用程序会在辅助线程上崩溃,但在主线程上没问题。
如果您想在后台线程上执行此工作,则必须增加该线程的堆栈大小。
例如:
// NSThread way:
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(populateDictionaries) object:nil];
thread.stackSize = 1024*1024;
[thread start];
或
// POSIX way:
#include <pthread.h>
static void *posixThreadFunc(void *arg) {
Dictionary *emojiDictionary = [[Dictionary alloc] init];
return NULL;
}
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
pthread_t posixThread;
pthread_attr_t stackSizeAttribute;
size_t stackSize = 0;
pthread_attr_init (&stackSizeAttribute);
pthread_attr_getstacksize(&stackSizeAttribute, &stackSize);
if (stackSize < 1024*1024) {
pthread_attr_setstacksize (&stackSizeAttribute, REQUIRED_STACK_SIZE);
}
pthread_create(&posixThread, &stackSizeAttribute, &posixThreadFunc, NULL);
}
@end
或
// Create mutable dictionary to prevent stack from overflowing
- (void)makeEmojiDictionaries {
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[@"arancia meccanica"] = @"⏰";
dict[@"uno freddo"] = @"";
dict[@"un drink"] = @"";
.....
self.englishEmojiAllDictionary = [dict copy];
}
仅供参考:
我有一个名为 Dictionary
的 class,其中 init
方法如下所示:
- (id) init{
self = [super init];
if (self){
[self makeEmojiDictionaries];
}
return self;
}
- (void)makeEmojiDictionaries{
//next line triggers bad_exc_access error
self.englishEmojiAllDictionary = @{@"hi" : @""}; //this is a strong, atomic property of NSDictionary
};
我的问题是实际的表情符号字典非常大,我想使用 GCD 在 非主线程 中完成所有繁重的工作。但是,每当我到达设置 self.englishEmojiAllDictionary
的行时,我总是会收到 bad_access
错误。
我正在以最正常的方式使用 GCD:
dispatch_queue_t myQueue = dispatch_queue_create("My Queue",NULL);
dispatch_async(myQueue, ^{
//Do long process activity
Dictionary *dictionary = [[Dictionary alloc] init];
});
GCD 或非主线程工作是否有我遗漏的特殊细微差别?非常感谢任何帮助 - 谢谢!
编辑 1:
如果您想自己尝试一下。我上传了一个复制此异常的 sample project。我的理论是我正在初始化的 NSDictionary
太大了。
我已将您的数据从代码移至 plist
文件,格式如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>arancia meccanica</key><string>⏰</string>
<key>uno freddo</key><string></string>
<key>un drink</key><string></string>
...
<key>bacio</key><string></string>
<key>baci</key><string></string>
</dict>
</plist>
(我拿了你的数据并使用了三次查找替换:",
=> </string>
,然后 ":@"
=> </key><string>
和 @"
=> <key>
).
然后我使用以下方法加载了数据:
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"dictionary"
ofType:@"plist"]
dictionary = [NSDictionary dictionaryWithContentsOfFile:filePath];
问题已解决。请注意,您应该永远不要将您的数据硬编码到源代码中。
错误的确切原因很难查明。 NSDictionary
文字使用方法 +[NSDictionary dictionaryWithObjects:forKeys:count:]
。
我的汇编程序知识很差,但我认为在调用这个初始化程序之前,所有的键和值都放在堆栈上。
但是,主线程的栈大小和后台线程的栈大小是有区别的(见Creating Threads in Thread Programming Guide)。
这就是为什么在后台线程上执行代码时会出现问题的原因。如果你有更多的数据,这个问题可能也会出现在主线程上。
主线程和后台线程栈大小的区别也可以通过下面简单的代码来说明:
- (void)makeEmojiDictionaries {
// allocate a lot of data on the stack
// (approximately the number of pointers we need for our dictionary keys & values)
id pointersOnStack[32500 * 2];
NSLog(@"%i", sizeof(pointersOnStack));
}
当您需要做一些缓慢的事情时,正确的模式是在后台队列中私下完成工作,然后分派回主队列以使完成的工作可供应用程序的其余部分使用。在这种情况下,您不需要创建自己的队列。您可以使用全局后台队列之一。
#import "ViewController.h"
#import "Dictionary.h"
@interface ViewController ()
@property (nonatomic, strong, readonly) Dictionary *dictionary;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self updateViews];
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
Dictionary *dictionary = [[Dictionary alloc] init];
dispatch_async(dispatch_get_main_queue(), ^{
_dictionary = dictionary;
[self updateViews];
});
});
}
- (void)updateViews {
if (self.dictionary == nil) {
// show an activity indicator or something
} else {
// show UI using self.dictionary
}
}
@end
从文件加载字典是个好主意,您可以在后台队列中执行此操作,然后将加载的字典分派回主线程。
首先,我建议你使用一个文件(plist, txt, xml, ...)来存储大数据,然后在运行时读取它,或者从远程服务器下载它。
对于你的问题,是因为堆栈大小的限制。 在 iOS 上,主线程的默认堆栈大小为 1 MB,辅助线程的默认堆栈大小为 512 KB。 您可以通过 [NSThread currentThread].stackSize
查看。
您的硬编码字典占用了将近 1 MB 的堆栈,这就是为什么您的应用程序会在辅助线程上崩溃,但在主线程上没问题。
如果您想在后台线程上执行此工作,则必须增加该线程的堆栈大小。
例如:
// NSThread way:
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(populateDictionaries) object:nil];
thread.stackSize = 1024*1024;
[thread start];
或
// POSIX way:
#include <pthread.h>
static void *posixThreadFunc(void *arg) {
Dictionary *emojiDictionary = [[Dictionary alloc] init];
return NULL;
}
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
pthread_t posixThread;
pthread_attr_t stackSizeAttribute;
size_t stackSize = 0;
pthread_attr_init (&stackSizeAttribute);
pthread_attr_getstacksize(&stackSizeAttribute, &stackSize);
if (stackSize < 1024*1024) {
pthread_attr_setstacksize (&stackSizeAttribute, REQUIRED_STACK_SIZE);
}
pthread_create(&posixThread, &stackSizeAttribute, &posixThreadFunc, NULL);
}
@end
或
// Create mutable dictionary to prevent stack from overflowing
- (void)makeEmojiDictionaries {
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[@"arancia meccanica"] = @"⏰";
dict[@"uno freddo"] = @"";
dict[@"un drink"] = @"";
.....
self.englishEmojiAllDictionary = [dict copy];
}
仅供参考: