在NSThread或NSOperation中使用NSUrlConnection

时间:2013-03-28 17:52:05

标签: ios nsurlconnection nsthread nsoperation synchronous

我正在开发一个静态库,需要在后台执行一些操作,而不需要与主线程进行交互。为了给你一个想法,想想只记录一些用户事件。库必须继续这样做,直到用户退出应用程序或将其发送到后台(按下主页按钮) - 换句话说,它需要继续在循环内做东西。

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

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

我应该将NSThread子类化并以某种方式使用NSUrlConnection?似乎异步NSUrlConnection在NSThread中不起作用,因为委托方法没有被调用但是同步方法呢?我是否需要使用和配置RunLoop并设置自动释放池?或者我应该使用NSOperation?在我看来,我想要做的事情非常普遍 - 有没有人有一个如何正确地做到这一点的工作实例?

2 个答案:

答案 0 :(得分:1)

据我了解,要异步使用NSURLConnection,您需要一个runloop。即使你使用NSOperation,你仍然需要一个runloop。

我见过的所有示例都使用Main Thread启动NSURLConnection,它有一个runloop。使用NSOperation的示例已设置,因此操作为Concurrent,告诉NSOperationQueue不提供自己的线程,然后确保在主线程上启动NSURLConnection ,例如通过调用performSelectorOnMainThread:

以下是一个例子:

Pulse Engineering Blog: Concurrent Downloads using NSOperationQueues

您还可以在QRunLoopOperation示例中搜索Apple文档中的LinkedImageFetcher,这是一个示例类,显示了此类事情的一些细节。

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

答案 1 :(得分: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
    }

}