使用NSBlockOperation和队列的NSURLSession [英] NSURLSession with NSBlockOperation and queues

查看:96
本文介绍了使用NSBlockOperation和队列的NSURLSession的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的应用目前使用 NSURLConnection 来覆盖其绝大多数网络。我想转到 NSURLSession 因为Apple告诉我这是要走的路。

I have an app that currently uses NSURLConnection for the vast majority of its networking. I would like to move to NSURLSession because Apple tells me that is the way to go.

我的应用只是使用同步版本的 NSURLConnection 通过 +(NSData *)sendSynchronousRequest:(NSURLRequest *)请求returnsResponse:(NSURLResponse **)响应错误: (NSError **)错误类方法。我在一个 NSBlockOperation 中运行 NSOperationQueue ,所以我不会不必要地阻塞主队列。以这种方式做事的最大好处是我可以使操作彼此依赖。例如,我可以让请求数据的任务依赖于登录任务的完成。

My app just uses the synchronous version of NSURLConnection by way of the + (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error class method. I do this within a NSBlockOperation running on an NSOperationQueue so I am not needlessly blocking the main queue. The big advantage to doing things this way is that I can make the operations dependent on one another. For example, I can have the task that is requesting data be dependent on the login task finishing.

我还没有在 NSURLSession 。所有我能找到的文章都是嘲笑我甚至想要同步使用它而且我是一个阻止线程的可怕人物。精细。但我认为没有办法让 NSURLSessionTask 相互依赖。有没有办法做到这一点?

I have not seen any support for synchronous operations within NSURLSession. All I can find are articles deriding me for even thinking of using it synchronously and that I am a horrible person for blocking the threads. Fine. But I see no way to make NSURLSessionTasks dependent on each other. Is there a way to do that?

或者是否有一种描述我将以不同的方式做这样的事情?

Or is there a description of how I would do such a thing in a different way?

推荐答案

同步网络请求的最严厉批评是为那些从主队列中执行它的人保留的(因为我们知道永远不应该阻塞主队列)。但是你在自己的后台队列中进行,它解决了同步请求中最令人震惊的问题。但是你失去了异步技术提供的一些很棒的功能(例如,如果需要,取消请求)。

The harshest criticisms of synchronous network requests are reserved for those who do it from the main queue (as we know that one should never block the main queue). But you're doing it on your own background queue, which addresses the most egregious problem with synchronous requests. But you're losing some wonderful features that asynchronous techniques provide (e.g. cancelation of requests, if needed).

我会回答你的问题(如何制作<$ c下面是$ c> NSURLSessionDataTask 同步行为),但我真的鼓励你接受异步模式,而不是打击它们。我建议重构你的代码以使用异步模式。具体来说,如果一个任务依赖于另一个任务,只需将依赖任务的启动放在先前任务的完成处理程序中。

I'll answer your question (how to make NSURLSessionDataTask behave synchronously) below, but I'd really encourage you to embrace the asynchronous patterns rather than fighting them. I'd suggest refactoring your code to use asynchronous patterns. Specifically, if one task is dependent upon another, simply put the initiation of the dependent task in the completion handler of the prior task.

如果您在该转换中遇到问题,然后发布另一个Stack Overflow问题,向我们展示你的尝试,我们可以帮你解决。

If you have problems in that conversion, then post another Stack Overflow question, showing us what you tried, and we can try to help you out.

如果你想要使异步操作同步,常见的模式是使用调度信号量,因此启动异步进程的线程可以在继续之前等待来自异步操作的完成块的信号。永远不要从主队列中执行此操作,但如果您从某个后台队列执行此操作,则它可能是一种有用的模式。

If you want to make an asynchronous operation synchronous, a common pattern is to use a dispatch semaphore so your thread that initiated the asynchronous process can wait for a signal from the completion block of the asynchronous operation before continuing. Never do this from the main queue, but if you're doing this from some background queue, it can be a useful pattern.

您可以使用以下命令创建信号量:

You can create a semaphore with:

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

然后,您可以将异步处理的完成块信号发送到信号量:

You can then have the completion block of the asynchronous process signal the semaphore with:

dispatch_semaphore_signal(semaphore);

然后您可以将代码置于完成块之外(但仍然在后台队列中,而不是主队列)等待该信号:

And you can then have the code outside of the completion block (but still on the background queue, not the main queue) wait for that signal:

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

所以,使用 NSURLSessionDataTask ,把所有在一起,可能看起来像:

So, with NSURLSessionDataTask, putting that all together, that might look like:

[queue addOperationWithBlock:^{

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    NSURLSession *session = [NSURLSession sharedSession]; // or create your own session with your own NSURLSessionConfiguration
    NSURLSessionTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (data) {
            // do whatever you want with the data here
        } else {
            NSLog(@"error = %@", error);
        }

        dispatch_semaphore_signal(semaphore);
    }];
    [task resume];

    // but have the thread wait until the task is done

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    // now carry on with other stuff contingent upon what you did above
]);

使用 NSURLConnection (现已弃用),你必须跳过一些箍来从后台队列发起请求,但是 NSURLSession 正常处理它。

With NSURLConnection (now deprecated), you have to jump through some hoops to initiate requests from a background queue, but NSURLSession handles it gracefully.

话虽如此,使用这样的块操作意味着操作不会响应取消事件(至少它们正在运行)。所以我通常使用块操作避开这种信号量技术,只是将数据任务包装在异步 NSOperation 子类中。然后你享受操作带来的好处,但你也可以让它们被取消。这是更多的工作,但更好的模式。

Having said that, using block operations like this means that the operations won't respond to cancellation events (while they're running, at least). So I generally eschew this semaphore technique with block operations and just wrap the data tasks in asynchronous NSOperation subclass. Then you enjoy the benefits of operations, but you can make them cancelable, too. It's more work, but a much better pattern.

例如:

//
//  DataTaskOperation.h
//
//  Created by Robert Ryan on 12/12/15.
//  Copyright © 2015 Robert Ryan. All rights reserved.
//

@import Foundation;
#import "AsynchronousOperation.h"

NS_ASSUME_NONNULL_BEGIN

@interface DataTaskOperation : AsynchronousOperation

/// Creates a operation that retrieves the contents of a URL based on the specified URL request object, and calls a handler upon completion.
///
/// @param  request                    A NSURLRequest object that provides the URL, cache policy, request type, body data or body stream, and so on.
/// @param  dataTaskCompletionHandler  The completion handler to call when the load request is complete. This handler is executed on the delegate queue. This completion handler takes the following parameters:
///
/// @returns                           The new session data operation.

- (instancetype)initWithRequest:(NSURLRequest *)request dataTaskCompletionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))dataTaskCompletionHandler;

/// Creates a operation that retrieves the contents of a URL based on the specified URL request object, and calls a handler upon completion.
///
/// @param  url                        A NSURL object that provides the URL, cache policy, request type, body data or body stream, and so on.
/// @param  dataTaskCompletionHandler  The completion handler to call when the load request is complete. This handler is executed on the delegate queue. This completion handler takes the following parameters:
///
/// @returns                           The new session data operation.

- (instancetype)initWithURL:(NSURL *)url dataTaskCompletionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))dataTaskCompletionHandler;

@end

NS_ASSUME_NONNULL_END

//
//  DataTaskOperation.m
//
//  Created by Robert Ryan on 12/12/15.
//  Copyright © 2015 Robert Ryan. All rights reserved.
//

#import "DataTaskOperation.h"

@interface DataTaskOperation ()

@property (nonatomic, strong) NSURLRequest *request;
@property (nonatomic, weak) NSURLSessionTask *task;
@property (nonatomic, copy) void (^dataTaskCompletionHandler)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error);

@end

@implementation DataTaskOperation

- (instancetype)initWithRequest:(NSURLRequest *)request dataTaskCompletionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))dataTaskCompletionHandler {
    self = [super init];
    if (self) {
        self.request = request;
        self.dataTaskCompletionHandler = dataTaskCompletionHandler;
    }
    return self;
}

- (instancetype)initWithURL:(NSURL *)url dataTaskCompletionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))dataTaskCompletionHandler {
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    return [self initWithRequest:request dataTaskCompletionHandler:dataTaskCompletionHandler];
}

- (void)main {
    NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:self.request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        self.dataTaskCompletionHandler(data, response, error);
        [self completeOperation];
    }];

    [task resume];
    self.task = task;
}

- (void)completeOperation {
    self.dataTaskCompletionHandler = nil;
    [super completeOperation];
}

- (void)cancel {
    [self.task cancel];
    [super cancel];
}

@end

其中:

//
//  AsynchronousOperation.h
//

@import Foundation;

@interface AsynchronousOperation : NSOperation

/// Complete the asynchronous operation.
///
/// This also triggers the necessary KVO to support asynchronous operations.

- (void)completeOperation;

@end

//
//  AsynchronousOperation.m
//

#import "AsynchronousOperation.h"

@interface AsynchronousOperation ()

@property (nonatomic, getter = isFinished, readwrite)  BOOL finished;
@property (nonatomic, getter = isExecuting, readwrite) BOOL executing;

@end

@implementation AsynchronousOperation

@synthesize finished  = _finished;
@synthesize executing = _executing;

- (instancetype)init {
    self = [super init];
    if (self) {
        _finished  = NO;
        _executing = NO;
    }
    return self;
}

- (void)start {
    if ([self isCancelled]) {
        self.finished = YES;
        return;
    }

    self.executing = YES;

    [self main];
}

- (void)completeOperation {
    self.executing = NO;
    self.finished  = YES;
}

#pragma mark - NSOperation methods

- (BOOL)isAsynchronous {
    return YES;
}

- (BOOL)isExecuting {
    @synchronized(self) {
        return _executing;
    }
}

- (BOOL)isFinished {
    @synchronized(self) {
        return _finished;
    }
}

- (void)setExecuting:(BOOL)executing {
    @synchronized(self) {
        if (_executing != executing) {
            [self willChangeValueForKey:@"isExecuting"];
            _executing = executing;
            [self didChangeValueForKey:@"isExecuting"];
        }
    }
}

- (void)setFinished:(BOOL)finished {
    @synchronized(self) {
        if (_finished != finished) {
            [self willChangeValueForKey:@"isFinished"];
            _finished = finished;
            [self didChangeValueForKey:@"isFinished"];
        }
    }
}

@end

这篇关于使用NSBlockOperation和队列的NSURLSession的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆