iOS启动后台线程

120
我在我的iOS设备中有一个小的sqlitedb。当用户按下按钮时,我从sqlite获取数据并向用户显示它。
我希望将这个获取部分放在后台线程中(以避免阻塞UI主线程)。我是这样做的 -
[self performSelectorInBackground:@selector(getResultSetFromDB :) withObject:docids];
获取和一点处理之后,我需要更新UI。但由于(作为良好的实践)我们不应该从后台线程执行UI更新。我在主线程上调用了一个选择器,如下所示 -
[self performSelectorOnMainThread:@selector(showResults)withObject:nil waitUntilDone:NO];
但我的应用程序在第一步中崩溃。即启动后台线程的方法。这不是在iOS中启动后台线程的方法吗?
更新1:在[self performSelectorInBackground....之后,我会得到这个堆栈跟踪,没有任何信息 -

enter image description here

更新2: 我甚至尝试了启动一个后台线程,像这样 - [NSThread detachNewThreadSelector:@selector(getResultSetFromDB:) toTarget:self withObject:docids];但我仍然得到相同的堆栈跟踪。

只是为了澄清,当我在主线程上执行此操作时,一切都很顺利...

更新3: 这是我正在尝试从后台运行的方法

- (void)getResultSetFromDB:(NSMutableArray *)toProceessDocids
{
    SpotMain *mirror = [[SpotMain alloc] init];
    NSMutableArray *filteredDocids = toProceessDocids;

    if(![gMediaBucket isEqualToString:@""])
        filteredDocids = [mirror FetchDocIdsForMediaBucketWithDocID:filteredDocids mBucket:gMediaBucket numRes:-1];
    if(![gMediaType isEqualToString:@""])
        filteredDocids = [mirror FetchDocIdsForMediaType:filteredDocids mediaType:gMediaType numRes:-1];
    if(![gPlatform isEqualToString:@""])
        filteredDocids = [mirror FetchDocIdsForPlatformID:filteredDocids platformId:@"1" numRes:-1];

    self.resultSet = [mirror FetchObjectFromDocid:filteredDocids];
    [filteredDocids release];
    [mirror release];

    [self performSelectorOnMainThread:@selector(showResults) withObject:nil waitUntilDone:NO];
    return;
}

你得到了什么错误/崩溃日志? - jtbandes
请查看我的更新... - Srikar Appalaraju
请问您能否展示一下您正在后台调用的方法?并确保对象“docids”被保留。 - Rog
是的,docidsretain 的。我已经将其放在 .h 文件中,作为 @property (nonatomic, retain) NSMutableArray *docids; - Srikar Appalaraju
不要在方法名前加上“get”;应该使用“resultSetFromDB:”。 - bbum
5个回答

273
如果您使用 performSelectorInBackground:withObject: 来创建一个新的线程,那么执行的选择器负责设置新线程的自动释放池、运行循环和其他配置细节。请参阅 Apple 的 Threading Programming Guide 中的 "Using NSObject to Spawn a Thread"
不过,最好使用Grand Central Dispatch
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [self getResultSetFromDB:docids];
});

GCD 是一项更新的技术,在内存开销和代码行数方面更加高效。


已更新,感谢Chris Nolet的建议,使得上述代码更简单,并保持了与苹果最新的 GCD 代码示例的同步。


酷!不知道这个。这也适用于 [NSThread detachNewThreadSelector:@selector.... 吗? - Srikar Appalaraju
是的。根据苹果文档,调用 performSelectorInBackground:withObject: "等同于调用 NSThreaddetachNewThreadSelector:toTarget:withObject: 方法,并将当前对象、选择器和参数对象作为参数传递。" - Scott Forbes
在这个问题上,(unsigned long)NULL0有区别吗? - Sti
4
注意:dispatch_get_global_queue函数的第二个参数是为将来扩展保留的。目前,您应始终将0作为此参数传递。 - Jawad Al Shaikh
我应该使用performSelectorOnMainThread来更新UI操作结果,还是有更一致的方法可以使用GCD更新UI? - Ilya Denisov
此外,您可以将标志从 DISPATCH_QUEUE_PRIORITY_DEFAULT 更改为 DISPATCH_QUEUE_PRIORITY_BACKGROUND - OhadM

11

使用GCD,这实际上非常容易。一个典型的工作流程可能是这样的:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
    dispatch_async(queue, ^{
        // Perform async operation
        // Call your method/function here
        // Example:
        // NSString *result = [anObject calculateSomething];
                dispatch_sync(dispatch_get_main_queue(), ^{
                    // Update UI
                    // Example:
                    // self.myLabel.text = result;
                });
    });

如果想了解更多关于GCD的内容,可以查看苹果文档


4

启用NSZombieEnabled以了解正在被释放和访问的对象。然后检查getResultSetFromDB:是否与此有关。还要检查docids里面是否有任何内容并且是否被保留。

这样,您就可以确保没有问题了。


请复制您在主线程上成功运行的代码行。 - Nicolas S
我在主线程中使用它,至少它会调用该方法而不是突然崩溃 - [self getResultSetFromDB:docids]; - Srikar Appalaraju
你按照我告诉你的启用了吗? - Nicolas S
在这一行中设置一个断点:_SpotMain *mirror = [[SpotMain alloc] init];_,然后告诉我它是否被触发,如果是的话,哪一行导致了崩溃。请启用zombies以便我们可以获得清晰的错误日志。 - Nicolas S
是的,我已经启用了僵尸对象检测。我得到了这个错误信息 - 2011-08-14 12:49:42.697 FLO[16211:707] *** -[FMResultSet release]: message sent to deallocated instance 0x2bff80 2011-08-14 12:49:42.697 FLO[16211:1607] *** __NSAutoreleaseNoPool(): Object 0x2c0cc0 of class __NSCFData autoreleased with no pool in place - just leaking。当我尝试从后台线程调用此方法时,我无法到达SpotMain *mirror...,它在进入后台线程后很快崩溃... - Srikar Appalaraju
这不是与僵尸有关的问题。问题在于,如果你使用 performSelectorInBackground:withObject 创建一个新线程,那么你的新线程将没有自动释放池(除非你手动创建一个)。因此会出现关于在没有自动释放池的情况下释放对象的错误消息。 - Scott Forbes

2

Swift 2.x答案:

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
        self.getResultSetFromDB(docids)
    }

2

iOS自带的默认sqlite库没有使用SQLITE_THREADSAFE宏进行编译,这可能是您的代码崩溃的原因。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接