在非主线程上设置一个大的 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];
}

仅供参考: