NSOperationQueue и проблема потоков - PullRequest
0 голосов
/ 11 сентября 2011

Ну, у меня серьезная проблема. У меня есть класс, который управляет соединениями с NSOperationQueue и делегировать методы для этого класса, которые на самом деле являются методами делегата NSURLConnection. Иногда мне нужно открыть viewController, который имеет ответ моего класса делегата, но он попадает в область действия функции делегата, заполняя UITableView, и приложение просто падает там. Я решил эту проблему с помощью @synchronized для viewlocontroller dealloc и делегатов NSURLConnection, но я не думаю, что это самый чистый способ, поскольку пользовательский интерфейс иногда зависает, когда я добавляю много информации в tableView. Итак, есть ли способ сделать это чисто?

- (void)viewDidLoad:(BOOL)animated {
[myManager startRequest];
}

//myManager class callback
- (void)managerDelegate {
//Doing things and just poped the viewController while in the function scope
//Program crashes, most of logs are "UIViewController message sent to deallocated instance"
}

//viewController dealloc
- (void)dealloc
{
    @synchronized([Manager class])
    {
    alert.delegate = nil;
    searchResultsTableView.delegate = nil;
    searchResultsTableView.dataSource = nil;
    [searchResultsTableView release]; searchResultsTableView = nil;
    [serviceSearch release]; serviceSearch = nil;
    [searchResults release]; searchResults = nil;
    [XMLTag release]; XMLTag = nil;
    [XMLParserServicesKeys release]; XMLParserServicesKeys = nil;
    [XMLParserKeys release]; XMLParserKeys = nil;
    [searchString release]; searchString = nil;
    [__managedObjectContext release]; __managedObjectContext = nil;
    manager.delegate = nil; 
    [manager stopAllRequests];
    [manager release]; manager = nil;
    [super dealloc];
    }
}

Редактировать: еще немного кода, теперь из класса myManager

- (void) stopAllRequests
{
#ifdef _WSMANAGER_DEBUG_
    NSLog(@"stopAllRequests %d", [connectionsArray count]);
#endif
    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];

    for(NNSURLConnection* connection in connectionsArray)
    {
        [connection cancel];
        [connection release];
    }


    [connectionsArray removeAllObjects];    
    [queue cancelAllOperations];
}


- (BOOL)startRequest
{   
//Data initialization       
    NSInvocationOperation* operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(beginConnection:) object:[NSDictionary dictionaryWithObjectsAndKeys:request, kRequestKey, keyInfo, kUserInfoKey, nil]];
    [queue addOperation:operation];
    [operation release];
    return YES;
}

-(void) beginConnection:(id)object
{   
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    NNSURLConnection* connection = [[NNSURLConnection alloc] initWithRequest:[object objectForKey:kRequestKey] delegate:self];

    if(connection)
    {
        NSMutableData *requestData = [[NSMutableData alloc] init];

        connection.url = [((NSURLRequest*)[object objectForKey:kRequestKey]) URL];

        connection.userInfo = [NSDictionary dictionaryWithObjectsAndKeys:[object objectForKey:kUserInfoKey], kUserInfoKey, requestData, kRequestDataKey, nil];
        [connectionsArray addObject:connection];

        [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
    }

    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:TIMEOUT]];

    if([connectionsArray indexOfObject:connection] != NSNotFound)
    {
        [connection cancel];

        if([delegate conformsToProtocol:@protocol(ManagerDelegate)] && [delegate respondsToSelector:@selector(managerFailed:withKey:errorCode:)]) {
            [delegate managerFailed:self withKey:[connection.userInfo objectForKey:kUserInfoKey] errorCode:ManagerErrorCodeTimeout];
            if([connectionsArray count] < 1)
                [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
        }

        [connectionsArray removeObject:connection];
    }
    [pool drain];
}

- (void)connectionDidFinishLoading:(NNSURLConnection *)connection {
    @synchronized([Manager class])
    {   
    NSMutableData *requestData = [connection.userInfo objectForKey:kRequestDataKey];
    NSString* responseString = [[NSString alloc] initWithData:requestData encoding:NSUTF8StringEncoding];       
    if([delegate conformsToProtocol:@protocol(PLMWSManagerDelegate)] && [delegate respondsToSelector:@selector(managerSuccess:responseString:withKey:)])
        [delegate managerSuccess:self responseString:responseString withKey:[connection.userInfo objectForKey:kUserInfoKey]];
    [responseString release];
    [requestData release];
    [connectionsArray removeObject:connection];
    [connection cancel];
    [connection release]; connection = nil;

    if([connectionsArray count] < 1)
        [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
    }
}

Ответы [ 3 ]

1 голос
/ 11 сентября 2011

Стандартный вступительный комментарий с учетом важности будущего: вклад Apple в проект LLVM предполагает, что iOS 5 будет иметь слабые ссылки на самообнуление.Таким образом, объекты могут содержать ссылки на другие объекты, не имея их, и когда эти объекты освобождаются, все указатели на них магически устанавливаются на nil.Нам придется дождаться публичного выпуска инструментов разработчика, чтобы выяснить, реализует ли iOS эту функциональность (для этого требуется некоторая поддержка во время выполнения по понятным причинам) и, если да, то использует ли ее Apple для делегатов.Однако это, скорее всего, произойдет довольно скоро, и, как разработчик, вы сможете получить версию инструментов NDA, которая доступна в настоящее время, поэтому, возможно, стоит рассмотреть этот путь к решению в зависимости от других практических аспектов вашего проекта.

Более полезно для здесь и сейчас:

Делегат для NSURLConnection s является неизменным.Предполагая, что NSURLConnection созданы задачами на NSOperationQueue, о которых вы упомянули, вы также можете создавать их на некотором цикле выполнения, отличном от того, который присоединен к основному потоку, что приводит к целому ряду проблем безопасности потоков в миксе.1009 *

Я бы посоветовал сделать следующее:

  1. убедитесь, что вы подключаете все NSURLConnection s к основному циклу выполнения.Для этого вам нужно создать соединения, чтобы они не запускались сразу, используйте scheduleInRunLoop:forMode:, чтобы назначить [NSRunLoop mainRunLoop] (и, вероятно, NSDefaultRunLoopMode), затем запустите их.
  2. одновременно, сохраните список всехсоединения, которые вы начали.Возможно, вы захотите использовать блок @synchronized, чтобы добавить его к подходящему NSMutableArray
  3. , когда объект, назначенный для получения методов делегата, должен быть освобожден, выполнить waitUntilAllOperationsAreFinished в очереди операций.а затем отправьте cancel всем действующим соединениям (например, [self.arrayOfConnections makeObjectsPerformSelector:@selector(cancel)])

Гарантируется, что соединения не будут связываться со своими делегатами после получения сообщения cancel.Вы хотите подождать, пока все операции в очереди не будут завершены, чтобы избежать возможного состояния гонки, из-за которого контроллер представления освобождается, успешно завершив все соединения в какое-то соответствующее время, но еще не завершенная операция затем пытается добавить новое.


Дополнительно, основываясь на обсуждении нашего комментария по вопросу:

NSURLConnection имеет встроенную систему для асинхронного запуска в цикле выполнения.Циклы выполнения - это версия цикла событий Apple, представляющая собой грубый список сообщений для публикации.Таким образом, они позволяют вам использовать один поток для выполнения множества задач, но только если ни одна вещь не блокирует поток.

Apple фактически рекомендует наиболее эффективный (с точки зрения обработки и времени работы от батареи) асинхронный процесс.URL-доступ обеспечивается за счет асинхронного запуска NSURLConnection в главном потоке.Вы можете настроить текущее значение -(void) startConnection на:

- (BOOL)startRequest
{   
    // we're going to do all this right here on the main thread, so there's
    // no need to package 'request' and 'keyInfo' into a dictionary and create
    // an operation

    // create the connection
    NNSURLConnection* connection = [[NNSURLConnection alloc] initWithRequest:request delegate:self];

    if(connection)
    {
        NSMutableData *requestData = [[NSMutableData alloc] init];

        connection.url = [keyInfo URL];

        connection.userInfo = [NSDictionary dictionaryWithObjectsAndKeys:keyInfo, kUserInfoKey, requestData, kRequestDataKey, nil];
        [connectionsArray addObject:connection];

        [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
    }

    // We used initWithRequest:delegate: so the request has already started.
    // The connection will now run asynchronously and we can wait for
    // delegate messages, which will be delivered via the runloop on this thread
}

Затем поместите все, что вы делали после того, как соединение закончилось, в ваш connectionDidFinishLoading::

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

    if([connectionsArray indexOfObject:connection] != NSNotFound)
    {
        [connection cancel];

        if([delegate conformsToProtocol:@protocol(ManagerDelegate)] && [delegate respondsToSelector:@selector(managerFailed:withKey:errorCode:)]) {
            [delegate managerFailed:self withKey:[connection.userInfo objectForKey:kUserInfoKey] errorCode:ManagerErrorCodeTimeout];
            if([connectionsArray count] < 1)
                [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
        }

        [connectionsArray removeObject:connection];
    }

    /* ...and all the other stuff here... */
}

У меня естьПовсюду предполагается, что NNSURLConnection является подклассом NSURLConnection, который просто добавляет несколько дополнительных свойств, которые никак не влияют на поведение.

1 голос
/ 11 сентября 2011

Когда приложение извлекает данные из ИНТЕРНЕТА для заполнения графического интерфейса, полное разделение потока графического интерфейса пользователя и интерфейса ИНТЕРНЕТ поможет полностью.Следующие варианты работали для меня в той же ситуации.

  1. Использовать – cancelAllOperations метод NSOperationQueue.это отменит вопрос после операции, которая выполняется в настоящее время.Если вы не отмените операцию, она будет работать в фоновом режиме.

  2. Сохранение структуры данных моста между таблицей и потоком INTERNET.Сохраните это в каком-то другом классе, а не в viewController, чтобы он не освобождался при извлечении viewcontroller.

  3. Заполните таблицу из этой структуры данных и заполните в ней новые доступные данные.Уведомлять таблицу, используя NSNotification ИЛИ каким-либо другим методом, когда новые данные доступны.Так что здесь, в этом случае, если вы продолжите работу очереди в фоновом режиме, она не будет обновлять tableView.

РЕДАКТИРОВАТЬ:

Более того, так как вы используете NSURLconnection, вы можете использовать - (void)cancel метод для отмены соединения.Делегат больше не будет получать звонки, как только вы отмените соединениеЭто может быть полезно двумя способами.

  1. Отмените последнюю операцию.

  2. Реализуйте что-то подобное в своем классе, который вызывает дочерний элемент пользовательского делегата.

Надеюсь, что приведенный выше метод полезен.

0 голосов
/ 11 сентября 2011

Возможно, есть очень веская причина, почему вы так поступаете, но почему вы не используете стороннюю библиотеку ASIHttpConnection?

Он управляет деталями соединения URL и даже имеет встроенную очередь операций.

Еще одна вещь, на которую следует обратить внимание, так как Томми Облики отмечает, что ARC готовится к выходу (ARC была публично объявлена, поэтому можно упомянуть, что она существует). Поскольку ARC поддерживает устройства 4.0, было бы ДЕЙСТВИТЕЛЬНО хорошей идеей перейти к этому, если это вообще возможно. Вы можете столкнуться с некоторыми проблемами, но это, вероятно, вылечит больше проблем, чем вызовет.

...