NSURLSession z Nsblockoperacja i kolejki

Mam aplikację, która obecnie używa NSURLConnection dla zdecydowanej większości swoich sieci. Chciałbym przenieść się do NSURLSession, ponieważ Apple mówi mi, że to jest droga do zrobienia.

Moja aplikacja używa synchronicznej wersji NSURLConnection za pomocą metody klasy + (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error. Robię to w ramach NSBlockOperation działającego na NSOperationQueue, więc nie niepotrzebnie blokuję kolejki głównej. Dużą zaletą robienia rzeczy w ten sposób jest to, że mogę uzależnić operacje od siebie. Na przykład, mogę mieć zadanie, które jest żądanie danych zależy od zakończenia zadania logowania.

Nie widziałem żadnego wsparcia dla operacji synchronicznych w NSURLSession. Wszystko, co mogę znaleźć, to artykuły wyśmiewające mnie za to, że nawet myślę o użyciu go synchronicznie i że jestem okropną osobą, która blokuje wątki. Dobrze. Ale nie widzę sposobu na uzależnienie się od siebie. Jest na to sposób?

A może jest jakiś opis, jak zrobiłbym coś takiego w inny sposób?

Author: Erik Allen, 2014-01-18

2 answers

Najsurowsza krytyka synchronicznych żądań sieciowych jest zarezerwowana dla tych, którzy robią to z kolejki głównej (ponieważ wiemy, że nigdy nie należy blokować kolejki głównej). Ale robisz to na własnej kolejce w tle, która rozwiązuje najbardziej skandaliczny problem z synchronicznymi żądaniami. Ale tracisz kilka wspaniałych funkcji, które zapewniają techniki asynchroniczne (np. anulowanie żądań, w razie potrzeby).

Odpowiem na twoje pytanie (Jak sprawić by NSURLSessionDataTask zachowywał się synchronicznie) poniżej, ale naprawdę zachęcam do korzystania z wzorców asynchronicznych, zamiast z nimi walczyć. Sugerowałbym refaktoryzację Twojego kodu, by używał asynchronicznych wzorców. W szczególności, jeśli jedno zadanie jest zależne od drugiego, po prostu umieść inicjację zadania zależnego w obsłudze zakończenia poprzedniego zadania.

Jeśli masz problemy z tą konwersją, wyślij kolejne pytanie o przepełnienie stosu, pokazując nam, czego próbowałeś, a my postaramy się ci pomóc.


Jeśli chcesz aby operacja asynchroniczna była synchroniczna, powszechnym wzorcem jest użycie semafora dyspozytorskiego, aby wątek, który zainicjował proces asynchroniczny, mógł poczekać na sygnał z bloku zakończenia operacji asynchronicznej przed kontynuowaniem. Nigdy nie rób tego z kolejki głównej, ale jeśli robisz to z kolejki w tle, może to być użyteczny wzór.

SEMAFOR można utworzyć za pomocą:

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

Możesz mieć blok zakończenia procesu asynchronicznego sygnalizacja semafora za pomocą:

dispatch_semaphore_signal(semaphore);

I możesz mieć kod poza blokiem zakończenia (ale nadal w kolejce w tle, a nie w kolejce głównej) czekać na ten sygnał:

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

Więc, z NSURLSessionDataTask, złożenie tego wszystkiego razem, może wyglądać tak:

[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
]);

Z NSURLConnection (obecnie przestarzałe), musisz przeskoczyć przez kilka obręczy, aby zainicjować żądania z kolejki w tle, ale NSURLSession obsługuje je z wdziękiem.


Powiedziawszy to, używając bloku operacje takie jak ta oznaczają, że operacje nie będą reagować na zdarzenia anulowania (przynajmniej podczas ich trwania). Tak więc generalnie unikam tej techniki semaforów za pomocą operacji blokowych i po prostu zawijam zadania danych w asynchroniczną podklasę NSOperation. Wtedy możesz cieszyć się korzyściami z operacji, ale możesz też je anulować. To więcej pracy, ale o wiele lepszy wzór.

Na przykład:

//
//  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

I

//
//  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

Gdzie:

//
//  AsynchronousOperation.h
//

@import Foundation;

@interface AsynchronousOperation : NSOperation

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

- (void)completeOperation;

@end

I

//
//  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
 105
Author: Rob,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-13 04:39:08

@Rob zachęcam do zamieszczenia odpowiedzi jako rozwiązania, w świetle poniższej notki z NSURLSession.dataTaskWithURL(_:completionHandler:):

[[1]}ta metoda jest przeznaczona jako alternatywa dla sendAsynchronousRequest:queue:completionHandler: method of NSURLConnection, z dodaną możliwością obsługi niestandardowych uwierzytelnianie i anulowanie.
 2
Author: Andrew Ebling,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-21 10:06:05