在非主线程上设置大型NSDictionary属性

时间:2016-03-23 04:59:18

标签: ios objective-c multithreading nsdictionary grand-central-dispatch

我有一个名为Dictionary的类,其中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太大了。

3 个答案:

答案 0 :(得分:2)

我已将您的数据从代码移到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>

(我使用了您的数据并使用了find-replace三次:", =&gt; </string>,然后":@" =&gt; </key><string>@" =&gt; <key>)。

然后我使用以下方法加载数据:

NSString *filePath = [[NSBundle mainBundle] pathForResource:@"dictionary" 
                                                     ofType:@"plist"]
dictionary = [NSDictionary dictionaryWithContentsOfFile:filePath];

这解决了这个问题。请注意,您从不将数据硬编码为源代码。

该错误的确切原因很难确定。 NSDictionary文字使用方法+[NSDictionary dictionaryWithObjects:forKeys:count:]。 我的汇编程序知识非常差,但我认为在调用这个初始化程序之前,所有键和&amp;值被放在堆栈上。

但是,主线程的堆栈大小与后台线程的堆栈大小之间存在差异(请参阅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));
}

答案 1 :(得分:2)

首先,我建议你使用一个文件(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];
}

仅供参考:

答案 2 :(得分:1)

当您需要做一些缓慢的事情时,正确的模式是在后台队列上私下进行工作,然后调度回主队列以使完成的工作可用于应用程序的其余部分。在这种情况下,您不需要创建自己的队列。您可以使用其中一个全局后台队列。

#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

从文件加载字典是个好主意,您可以在后台队列中执行此操作,然后使用加载的字典将其分派回主线程。