使用NSBlockOperation和队列的NSURLSession

Eri*_*len 53 objective-c ios nsblockoperation nsurlsession

我有一个应用程序,目前NSURLConnection用于其绝大多数网络.我想搬到,NSURLSession因为Apple告诉我这是要走的路.

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

我没有看到任何对同步操作的支持NSURLSession.所有我能找到的文章都是嘲笑我甚至想要同步使用它而且我是一个阻止线程的可怕人物.精细.但我认为没有办法让NSURLSessionTask彼此依赖.有没有办法做到这一点?

或者有描述我将如何以不同的方式做这样的事情?

Rob*_*Rob 108

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

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

如果您在转换中遇到问题,请发布另一个Stack Overflow问题,告诉我们您尝试了什么,我们可以尝试帮助您.


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

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

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
Run Code Online (Sandbox Code Playgroud)

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

dispatch_semaphore_signal(semaphore);
Run Code Online (Sandbox Code Playgroud)

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

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
Run Code Online (Sandbox Code Playgroud)

所以,NSURLSessionDataTask把它们放在一起,可能看起来像:

[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
]);
Run Code Online (Sandbox Code Playgroud)

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


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

例如:

//
//  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
Run Code Online (Sandbox Code Playgroud)

//
//  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
Run Code Online (Sandbox Code Playgroud)

哪里:

//
//  AsynchronousOperation.h
//

@import Foundation;

@interface AsynchronousOperation : NSOperation

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

- (void)completeOperation;

@end
Run Code Online (Sandbox Code Playgroud)

//
//  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
Run Code Online (Sandbox Code Playgroud)

  • @ErikAllen绝对没有什么可以阻止你在同时的`NSOperation`子类中包装`NSURLSessionTask`,然后享受依赖(如果做许多并发请求,控制并发程度).如果你只想要一个简单的"当登录任务完成后,启动另一个任务",你可以使用`dataTaskWithURL`和`completionHandler`作为登录任务的再现,并在`completionHandler中,启动下一个任务. (2认同)