Eri*_*ric 22 cocoa-touch ios nsurlsession
制作串行队列的最佳做法是NSURLSessionTasks什么?
就我而言,我需要:
NSURLSessionDataTask)NSURLSessionDownloadTask)这是我到目前为止所拥有的:
session = [NSURLSession sharedSession];
//Download the JSON:
NSURLRequest *dataRequest = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *task =
[session dataTaskWithRequest:dataRequest
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
//Figure out the URL of the file I want to download:
NSJSONSerialization *json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
NSURL *downloadURL = [NSURL urlWithString:[json objectForKey:@"download_url"]];
NSURLSessionDownloadTask *fileDownloadTask =
[session downloadTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:playlistURL]]
completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
NSLog(@"completed!");
}];
[fileDownloadTask resume];
}
];
Run Code Online (Sandbox Code Playgroud)
除了在另一个完成中写一个完成块看起来很乱的事实,我打电话时得到一个EXC_BAD_ACCESS错误[fileDownloadTask resume]...即使fileDownloadTask不是零!
那么,最好的测序方法是NSURLSessionTasks什么?
mat*_*tes 18
您需要使用这种最直接的方法:https://stackoverflow.com/a/31386206/2308258
或者使用操作队列并使任务彼此依赖
================================================== =====================
关于HTTPMaximumConnectionsPerHost方法
实现NSURLSessionTasks的先进先出串行队列的简单方法是在NSURLSession上运行其HTTPMaximumConnectionsPerHost属性设置为1的所有任务
HTTPMaximumConnectionsPerHost仅确保一个共享连接将用于该会话的任务,但这并不意味着它们将被串行处理.
您可以使用http://www.charlesproxy.com/在网络级别验证,您会发现在设置HTTPMaximumConnectionsPerHost时,您的任务仍将由NSURLSession同时启动,而不是按照相信的顺序启动.
实验1:
使用task2:url = download.thinkbroadband.com/20MB.zip
结果:调用task1 completionBlock,然后调用task2 completionBlock
如果您尝试使用相同的NSURLSession下载两个不同的东西,您可能会按照预期的顺序调用完成块,但是如果您尝试使用相同的NSURLSession下载两个不同的东西,您会发现NSURLSession没有任何基本的调用顺序但只有首先完成任何完成.
实验2:
task2:url = download.thinkbroadband.com/10MB.zip(较小的文件)
结果:调用task2 completionBlock,然后调用task1 completionBlock
总而言之,您需要自己进行排序,NSURLSession没有任何关于排序请求的逻辑,即使将每个主机的最大连接数设置为1,它也会先调用任何完成的completionBlock.
PS:很抱歉我发布的帖子格式没有足够的声誉来发布截图.
Eri*_*ric 13
编辑:
正如mataejoon指出的那样,设置HTTPMaximumConnectionsPerHost为1并不能保证连接是连续处理的.如果你需要一个可靠的串行队列,请尝试不同的方法(如我原来的答案)NSURLSessionTask.
实现先进先出串行队列的一种简单方法NSURLSessionTasks是在NSURLSession其HTTPMaximumConnectionsPerHost属性设置为的情况下运行所有任务1:
+ (NSURLSession *)session
{
static NSURLSession *session = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
[configuration setHTTPMaximumConnectionsPerHost:1];
session = [NSURLSession sessionWithConfiguration:configuration];
});
return session;
}
Run Code Online (Sandbox Code Playgroud)
然后按照您想要的顺序向其添加任务.
NSURLSessionDataTask *sizeTask =
[[[self class] session] dataTaskWithURL:url
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
Run Code Online (Sandbox Code Playgroud)
我使用 NSOperationQueue (正如欧文建议的那样)。将 NSURLSessionTasks 放入 NSOperation 子类中并设置任何依赖项。依赖任务会等到它们所依赖的任务完成后才运行,但不会检查状态(成功或失败),因此添加一些逻辑来控制流程。
就我而言,第一个任务检查用户是否拥有有效的帐户,并在必要时创建一个帐户。在第一个任务中,我更新 NSUserDefault 值以指示帐户有效(或存在错误)。第二个任务检查 NSUserDefault 值,如果一切正常,则使用用户凭据将一些数据发布到服务器。
(将 NSURLSessionTasks 粘在单独的 NSOperation 子类中也使我的代码更易于导航)
将 NSOperation 子类添加到 NSOperationQueue 并设置所有依赖项:
NSOperationQueue *ftfQueue = [NSOperationQueue new];
FTFCreateAccount *createFTFAccount = [[FTFCreateAccount alloc]init];
[createFTFAccount setUserid:@"********"]; // Userid to be checked / created
[ftfQueue addOperation:createFTFAccount];
FTFPostRoute *postFTFRoute = [[FTFPostRoute alloc]init];
[postFTFRoute addDependency:createFTFAccount];
[ftfQueue addOperation:postFTFRoute];
Run Code Online (Sandbox Code Playgroud)
在第一个 NSOperation 子类中检查服务器上是否存在帐户:
@implementation FTFCreateAccount
{
NSString *_accountCreationStatus;
}
- (void)main {
NSDate *startDate = [[NSDate alloc] init];
float timeElapsed;
NSString *ftfAccountStatusKey = @"ftfAccountStatus";
NSString *ftfAccountStatus = (NSString *)[[NSUserDefaults standardUserDefaults] objectForKey:ftfAccountStatusKey];
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setValue:@"CHECKING" forKey:ftfAccountStatusKey];
// Setup and Run the NSURLSessionTask
[self createFTFAccount:[self userid]];
// Hold it here until the SessionTask completion handler updates the _accountCreationStatus
// Or the process takes too long (possible connection error)
while ((!_accountCreationStatus) && (timeElapsed < 5.0)) {
NSDate *currentDate = [[NSDate alloc] init];
timeElapsed = [currentDate timeIntervalSinceDate:startDate];
}
if ([_accountCreationStatus isEqualToString:@"CONNECTION PROBLEM"] || !_accountCreationStatus) [self cancel];
if ([self isCancelled]) {
NSLog(@"DEBUG FTFCreateAccount Cancelled" );
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setValue:@"ERROR" forKey:ftfAccountStatusKey];
}
}
Run Code Online (Sandbox Code Playgroud)
在下一个 NSOperation 发布数据中:
@implementation FTFPostRoute
{
NSString *_routePostStatus;
}
- (void)main {
NSDate *startDate = [[NSDate alloc] init];
float timeElapsed;
NSString *ftfAccountStatusKey = @"ftfAccountStatus";
NSString *ftfAccountStatus = (NSString *)[[NSUserDefaults standardUserDefaults] objectForKey:ftfAccountStatusKey];
if ([ftfAccountStatus isEqualToString:@"ERROR"])
{
// There was a ERROR in creating / accessing the user account. Cancel the post
[self cancel];
} else
{
// Call method to setup and run json post
// Hold it here until a reply comes back from the operation
while ((!_routePostStatus) && (timeElapsed < 3)) {
NSDate *currentDate = [[NSDate alloc] init];
timeElapsed = [currentDate timeIntervalSinceDate:startDate];
NSLog(@"FTFPostRoute time elapsed: %f", timeElapsed);
}
}
if ([self isCancelled]) {
NSLog(@"FTFPostRoute operation cancelled");
}
}
Run Code Online (Sandbox Code Playgroud)