0

我正在开发一个静态库,它需要在后台做一些事情,而不与主线程交互。为了给您一个想法,请考虑仅记录一些用户事件。库必须继续做这些事情,直到用户退出应用程序或将其发送到后台(按下主页按钮) - 换句话说,它需要在循环中继续做事情。

主应用程序线程和衍生线程之间的唯一交互是,有时主应用程序线程会将一些东西(事件对象)放入衍生线程可以读取/使用的队列中。除此之外,生成的线程只会继续运行,直到应用程序存在或背景。

生成的线程需要做的部分事情(尽管不是全部)涉及将数据发送到 HTTP 服务器。我原以为很容易继承 NSThread,重写它的 main 方法,然后在该连接上使用某种超时对 NSUrlConnection 进行同步调用,这样线程就不会永远挂起。例如,在 Java/Android 中,我们只是子类化 Thread,覆盖 start() 方法并调用同步 HTTP GET 方法(例如来自 Apache 的 HttpClient 类)。这很容易并且工作正常。但是从我在这里和其他地方看到的情况来看,显然在 iOS 上它比这要复杂得多,而且我对实际可行的最佳方法有点困惑。

那么我应该继承 NSThread 并以某种方式使用 NSUrlConnection 吗?似乎异步 NSUrlConnection 在 NSThread 中不起作用,因为没有调用委托方法,但是同步方法呢?我是否需要以某种方式使用和配置 RunLoop 并设置自动释放池?还是我应该使用 NSOperation?在我看来,我正在尝试做的事情很常见 - 有没有人有一个如何正确执行此操作的工作示例?

4

2 回答 2

1

据我了解,要NSURLConnection异步使用,您需要一个运行循环。即使你使用 anNSOperation你仍然需要一个运行循环。

我见过的所有示例都使用具有运行循环的Main Threadto start NSURLConnection。使用的示例NSOperation已设置,因此操作Concurrent告诉NSOperationQueue不要提供它自己的线程,然后他们确保NSURLConnection在主线程上启动,例如通过调用performSelectorOnMainThread:

这是一个例子:

Pulse Engineering 博客:使用 NSOperationQueues 进行并发下载

QRunLoopOperation您还可以在示例中搜索 Apple 文档,LinkedImageFetcher这是一个示例类,显示了此类事物的一些来龙去脉。

(虽然我不确定我是否真的看到了该示例显示如何运行您自己的 runloop 的任何代码,但此示例再次依赖于主线程。)

于 2013-04-04T11:12:43.660 回答
0

我使用了大中央调度(GCD)方法来实现这一点。这是一个在一个简单的测试应用程序中对我有用的示例(我不确定它是否适用于静态库,但可能值得一看)。我正在使用ARC。

在示例中,我从 viewDidLoad 方法开始了一些后台工作,但您可以从任何地方开始它。关键是“dispatch_async(dispatch_get_global_queue ...”在后台线程中运行该块。有关该方法的良好解释,请参阅此答案:https ://stackoverflow.com/a/12693409/215821

这是我的viewDidLoad:

- (void)viewDidLoad
{
    [super viewDidLoad];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long)NULL), 
        ^(void) {
            [self doStuffInBackground];
        });
}

此时 doStuffInBackground 方法正在后台运行,因此您可以同步使用 NSURLConnection 。在我的示例中,该方法循环进行网络调用,直到可能有其他代码设置 backgroundStuffShouldRun = false。使用 10 秒超时进行网络调用。通话结束后,我正在更新 UI 标签以显示进度。请注意,UI 更新是使用“dispatch_async(dispatch_get_main_queue()...”执行的。这会根据需要在 UI 线程上运行 UI 更新。

此背景工作的一个潜在问题是:无法取消 http 请求本身。但是,如果有 10 秒的超时,那么在外部人员(可能是您的 UI 中的某些事件)设置 backgroundStuffShouldRun = false 之后,您将等待最多 10 秒的时间让线程自行中止。

- (void)doStuffInBackground
{
    while (backgroundStuffShouldRun) {
        // prepare for network call...
        NSURL* url = [[NSURL alloc] initWithString:@"http://maps.google.com/maps/geo"];

        // set a 10 second timeout on the request
        NSURLRequest* request = [[NSURLRequest alloc] initWithURL:url cachePolicy:NSURLCacheStorageAllowed timeoutInterval:10];

        NSError* error = nil;
        NSURLResponse *response = nil;

        // make the request
        NSData* data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];

        // were we asked to stop the background processing?
        if (!backgroundStuffShouldRun) {
            return;
        }

        // process response...

        NSString* status = @"Success";

        if (error) {
            if (error.code == NSURLErrorTimedOut) {
                // handle timeout...
                status = @"Timed out";
            }
            else {
                // handle other errors...
                status = @"Other error";
            }
        }
        else {
            // success, handle the response body
            NSString *dataAsString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            NSLog(@"%@", dataAsString);
        }


        // update the UI with our status
        dispatch_async(dispatch_get_main_queue(), ^{
            [statusLabel setText:[NSString stringWithFormat:@"completed network call %d, status = %@", callCount, status]];
        });

        callCount++;
        sleep(1); // 1 second breather. not necessary, but good idea for testing
    }

}
于 2013-04-10T01:08:18.147 回答