在分离的 NSThread 中分配内存以在后台加载 NSDictionary?

Posted

技术标签:

【中文标题】在分离的 NSThread 中分配内存以在后台加载 NSDictionary?【英文标题】:Memory allocation in detached NSThread to load an NSDictionary in background? 【发布时间】:2010-04-24 04:57:17 【问题描述】:

我正在尝试启动后台线程以从 Web 服务中检索 XML 数据。我同步开发它 - 没有线程,所以我知道那部分有效。现在我已经准备好通过生成一个线程来等待响应和解析来提供非阻塞服务了。

我在线程内创建了一个 NSAutoreleasePool 并在解析结束时释放它。生成的代码和线程如下:

从主循环代码生成

 .
 .
 [NSThread detachNewThreadSelector:@selector(spawnRequestThread:) 
                          toTarget:self withObject:url];
 .
 .

线程(在“自我”内部)

-(void) spawnRequestThread: (NSURL*) url  

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

 parser = [[NSXMLParser alloc] initWithContentsOfURL:url];

 [self parseContentsOfResponse];

 [parser release];
 [pool release];

parseContentsOfResponse 方法用解析后的文档内容填充 NSMutableDictionary。我想避免大量移动数据并将其分配回产生线程的主循环中,而不是制作副本。首先,这可能吗,如果没有,我可以简单地从主线程传入一个分配的指针并使用“dictionaryWithDictionary”方法进行分配吗?这看起来效率太低了。

parseContentsOfResponse

-(void)parseContentsOfResponse 

    [parser setDelegate:self];
    [parser setShouldProcessNamespaces:YES];
    [parser setShouldReportNamespacePrefixes:YES];

    [parser parse];

    NSError *parseError = [parser parserError];
    if (parseError) 
        NSLog(@"%@", parseError);
        NSLog(@"publicID: %@", [parser publicID]);
        NSLog(@"systemID: %@", [parser systemID]);
        NSLog(@"line:%d column:%d", [parser lineNumber], [parser columnNumber]);
    

    ready = YES;

第一个解析部分

每个部分都会在其 elementStart 发出信号时创建元素字符串。 elementEnd 会将对象添加到字典并释放元素。其余细节是多余的,我认为需要注意的一点是分配不是针对 NSZone,因此它们应该驻留在线程的内存池中。

- (void)parserDidStartDocument:(NSXMLParser *)parser 
    NSLog(@"%s", __FUNCTION__);
    currentChars    = [NSMutableString stringWithString:@""];
    elementQuestion = [NSMutableString stringWithString:@""];
    elementAnswer   = [NSMutableString stringWithString:@""];
    elementKeyword  = [NSMutableString stringWithString:@""];

【问题讨论】:

你能显示更多代码吗?你说parseContentsOfResponse 移动数据,但你没有显示。 我为文档添加了 parseContents 和第一个“元素解析”。其余的几乎都是样板编码,并将结果元素填充到字典中,以节点的名称作为键。 【参考方案1】:

最简单的做法是在单独的线程中创建字典,然后将其设置为主线程上的属性,如下所示:

- (void)spawnRequestThread: (NSURL*) url  
    NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
    //do stuff with dict
    [self performSelectorOnMainThread:@selector(doneWithThread:) withObject:dict waitUntilDone:NO];


- (void)doneWithThread:(NSDictionary *)theDict 
    self.dict = theDict; //retaining property, can be an NSDictionary

您是否需要随着时间的推移更改字典的内容?如果是这样,则可以在主线程上分配并更改另一个线程中的内容,但是您必须担心线程安全问题--NSMutableDictionary 不是线程安全的,因此您必须使用原子属性和锁:

//.h
@property (retain) NSMutableDictionary *dict; //don't use "nonatomic" keyword
@property (retain) NSLock *dictLock;

//.m
- (id) init 
    //blah blah
    dict = [[NSMutableDictionary alloc] init];
    dictLock = [[NSLock alloc] init];
    return self;


- (void)spawnRequestThread: (NSURL*) url  
    //whenever you have to update the dictionary
    [self.dictLock lock];
    [self.dict setValue:foo forKey:bar];
    [self.dictLock unlock];

在任何情况下,锁定都非常昂贵且效率低下,因此我倾向于使用第一种方法(我不确定哪种方法更昂贵,确切地说,但第一种更简单并且避免了线程安全问题)。

此外,查看您的代码,您的 NSXMLParser 似乎是您可以直接访问的 ivar。这是个坏主意,因为NSXMLParser isn't thread-safe--我建议将其实现为局部变量。如果您确实需要它作为 ivar,请使用原子属性和锁,并且只能通过访问器访问它。

【讨论】:

太棒了 --- 所以 performSelectorOnMainThread 会让我从后台线程返回到主线程,这样我就可以将字典更新存入它的内存空间 --- 你的建议理解正确吗? 是的,我最终会对字典进行“更新”。目前,它是应用程序开头的一个 blob。下一步是保存到用户空间,重新加载并检查更新。所以,我将不得不管理锁定。 关于 NSXMLParser 作为 ivar 的好建议。我想我可以通过对线程模型的重构轻松地将其放入本地范围。同时,我只使用访问器——但我不能相信我以后修改应用程序时会记住这一点:) 是的,performSelectorOnMainThread 让你回到主线程(它是所有线程的相同内存空间,但某些操作对主线程以外的线程是不安全的,例如访问非线程安全对象或更新 UI)。想一想,答案中的第二种方法仍然不安全——无论何时更改字典的内容,您仍然必须实现锁定(我将更新示例)。 该死 - 现在我必须思考 :( 我真的很想避免锁定并依赖模态视图和原子“就绪”标志。这是非常有帮助和教育意义的。我希望你的回答得到很多赞成票。

以上是关于在分离的 NSThread 中分配内存以在后台加载 NSDictionary?的主要内容,如果未能解决你的问题,请参考以下文章

分离的 NSThread 内存泄漏

遍历 BSTR 的 VARIANT/SAFEARRAY 以在 C++ 中分配值并打印到控制台

如果您在进程崩溃后在进程中分配内存会发生啥?

java中的全局变量和静态变量是在编译时分配内存还是在加载时分配内存??

在linux中分配物理内存缓冲区

实例变量未在内存中分配