Как начать асинхронное NSURLConnection внутри NSOperation?

Я хочу сделать Asynchrous NSURLConnection внутри NSOperation в фоновом потоке. это потому, что я делаю очень дорогие операции над данными по мере их возвращения.

Это очень похожий вопрос на то, что они задавали здесь: как мне сделать асинхронный NSURLConnection внутри NSOperation?

но разница в том, что я запускаю соединение в другом классе.

Вот моя первая попытка:

В моем MainViewController:

@property (nonatomic, strong) NSOperationQueue *requestQueue;

#pragma mark - Lazy initialization
- (NSOperationQueue *)requestQueue 
{
    if (!_requestQueue) {
        _requestQueue = [[NSOperationQueue alloc] init];
        _requestQueue.name = @"Request Start Application Queue";
        _requestQueue.maxConcurrentOperationCount = 1;
    }
    return _requestQueue;
}

-(void)callToServer
{
URLJsonRequest *request = [URLRequestFactory createRequest:REQUEST_INTERFACE_CLIENT_VERSION
                                                         delegate:self];

    RequestSender *requestSender = [[RequestSender alloc]initWithPhotoRecord:request delegate:self];

   [self.requestQueue addOperation:requestSender];
}

Вот моя операция:

- (id)initWithPhotoRecord:(URLJsonRequest *)request
                 delegate:(id<RequestSenderDelegate>) theDelegate{

    if (self = [super init])
    {
        self.delegate = theDelegate;
        self.jsonRequest = request;
    }
    return self;
}

- (void)main {

    //Apple recommends using @autoreleasepool block instead of alloc and init NSAutoreleasePool, because blocks are more efficient. You might use NSAuoreleasePool instead and that would be fine.
    @autoreleasepool
    {

        if (self.isCancelled)
            return;

        [self.jsonRequest start];

    }
}

Вот моя функция запуска запроса:

-(void) start
{
  NSURL *url = [NSURL URLWithString:@"http://google.com"];
 NSURLRequest *theRequest = [NSURLRequest requestWithURL:url];
  urlConnection = [[[NSURLConnection alloc]    initWithRequest:theRequest delegate:self]autorelease];

[urlConnection start];
[theRequest release]
}


- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
    NSLog(@"Received reponse from connection");
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{



}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection{

}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{

}

Я не получаю ответа от сервера.

Ответ 1

Несколько подходов:

  • Запланируйте NSURLConnection в цикле основного запуска, используя параметр startImmediately NO, установите цикл выполнения и только тогда вы должны начать соединение, например:

    urlConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self startImmediately:NO];
    [urlConnection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
    [urlConnection start];
    
  • Создайте выделенный поток для соединения и запланируйте соединение в цикле запуска, который вы создаете для этого потока. См. AFURLConnectionOperation.m в AFNetworking источник для примера.

  • Фактически используйте AFNetworking, который дает вам операции NSOperation, которые вы можете добавить в свою очередь, и берет на себя заботу этого цикла для вас.


Итак, AFNetworking делает что-то вроде:

+ (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"NetworkingThread"];

        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
    }
}

+ (NSThread *)networkRequestThread {
    static NSThread *_networkRequestThread = nil;
    static dispatch_once_t oncePredicate;

    dispatch_once(&oncePredicate, ^{
        _networkRequestThread = [[NSThread alloc] initWithTarget:self
                                                        selector:@selector(networkRequestThreadEntryPoint:)
                                                          object:nil];
        [_networkRequestThread start];
    });

    return _networkRequestThread;
}

Итак, я делаю что-то вроде следующего. Сначала у меня есть несколько частных свойств:

@property (nonatomic, readwrite, getter = isExecuting)  BOOL executing;
@property (nonatomic, readwrite, getter = isFinished)   BOOL finished;
@property (nonatomic, weak)   NSURLConnection *connection;

Затем сетевая операция может сделать что-то вроде:

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

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

- (void)start {
    if (self.isCancelled) {
        [self completeOperation];
        return;
    }

    self.executing = YES;

    [self performSelector:@selector(startInNetworkRequestThread)
                 onThread:[[self class] networkRequestThread]
               withObject:nil
            waitUntilDone:NO];
}

- (void)startInNetworkRequestThread {
    NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:self.request
                                                                  delegate:self
                                                          startImmediately:NO];
    [connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    [connection start];

    self.connection = connection;
}

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

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

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

- (BOOL)isConcurrent {
    return YES;
}

- (BOOL)isAsynchronous {
    return YES;
}

// all of my NSURLConnectionDataDelegate stuff here, for example, upon completion:

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    // I call the appropriate completion blocks here, do cleanup, etc. and then, when done:

    [self completeOperation];
}

Ответ 2

-(void)start
{
  [self willChangeValueForKey:@"isExecuting"];
  _isExecuting = YES;
  [self didChangeValueForKey:@"isExecuting"];
  NSURL* url = [[NSURL alloc] initWithString:@"http://url.to/feed.xml"];
  NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20];
  _connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]; // ivar
  [request release];
  [url release];
  // Here is the trick
  NSPort* port = [NSPort port];
  NSRunLoop* rl = [NSRunLoop currentRunLoop]; // Get the runloop
  [rl addPort:port forMode:NSDefaultRunLoopMode];
  [_connection scheduleInRunLoop:rl forMode:NSDefaultRunLoopMode];
  [_connection start];
  [rl run];
}

Более подробную информацию можно найти здесь: ссылка

Ответ 3

Я знаю, что этот пост старше года, но я хотел добавить некоторые предложения для людей, которые могут запускать ту же самую проблему, пытаясь создать собственную работу сети async. Вам нужно добавить runloop к операции, которая работает в фоновом режиме, и вы должны остановить ее, когда закончите работу.

На самом деле есть две простые опции:

Вариант 1 - с помощью NSRunLoop

NSPort *port        = [NSPort port];
NSRunLoop *runLoop  = [NSRunLoop currentRunLoop];
[runLoop addPort:port forMode:NSDefaultRunLoopMode];
[self.connection scheduleInRunLoop:runLoop forMode:NSDefaultRunLoopMode];
[self.connection start];
[runLoop run];

и вам нужно прекратить выполнение операции:

NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
NSDate *date       = [NSDate distantFuture];
while (!runLoopIsStopped && [runLoop runMode:NSDefaultRunLoopMode beforeDate:date]);

Вариант 2 - с использованием CF

Вам нужно добавить

CFRunLoopRun();

при запуске операции

и вызовите

CFRunLoopStop(CFRunLoopGetCurrent());

когда вы закончите работу.

Прочитайте следующее сообщение: CFRunLoopRun() vs [NSRunLoop run]