Использование Singleton синхронизированного массива с NSThread - PullRequest
5 голосов
/ 27 февраля 2011

У меня есть приложение для книг с UISearchBar, где пользователь вводит любое имя книги и получает результаты поиска (из вызова ext API) ниже по мере ввода.

В моем приложении используется переменная-одиночка, которая называетсяretrievedArray, в котором хранятся все книги.

@interface Shared : NSObject {
    NSMutableArray *books;
}

@property (nonatomic, retain) NSMutableArray *books;

+ (id)sharedManager;

@end

Доступ к нему осуществляется в нескольких файлах .m с использованием NSMutableArray * retrievedArray;... в заголовочном файле

retrievedArray = [[Shared sharedManager] books];

Мой вопрос заключается в том, как обеспечить, чтобы значения внутри retrievedArray оставались синхронизированными по всем классам.

Фактически значения внутри retrievedArray добавляются черезNSXMLParser (т.е. через API внешнего веб-сервиса).Существует отдельный файл XMLParser.m, где я делаю весь анализ и заполняю массив.Синтаксический анализ выполняется в отдельном потоке.

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

        NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL: [self URL]];
        [parser setDelegate: self];
    [parser parse];
        [parser release];

        NSString *tmpURLStr = [[self URL]absoluteString];

        NSRange range_srch_book = [tmpURLStr rangeOfString:@"v1/books"];

        if (range_srch_book.location != NSNotFound) 
            [delegate performSelectorOnMainThread:@selector(parseDidComplete_srch_book) withObject:nil waitUntilDone:YES];

        [pool release];
    } 


    - (void) parseXMLFile: (NSURL *) url
    {   
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        [self setURL: url];
        NSThread* myThread = [[NSThread alloc] initWithTarget:self
                                                     selector:@selector(run:)


object: nil];
    [retrievedArray removeAllObjects];
    [myThread start];
    [pool release];
}

Кажется, что есть некоторые проблемы с синхронизацией, если пользователь печатает очень быстро (кажется, работает нормально, если пользователь печатает медленно) .... Так что2 вида, в которых отображается содержимое объекта в этом элементе общего массива;Список и детали.Если пользователь быстро набирает и нажимает A в представлении «Список», он отображается в виде «B». Это основная проблема.

Я буквально попробовал все решения, которые мог придумать, но до сих пор не могучтобы исправить проблему.

ПРИМ. РЕДАКТИРОВАНИЯ ВЫПУСКА СИНХРОНИЗАЦИИ: В представлении списка, если отображаются 3 элемента, скажем Item1, Item2 и Item3, и если пользователь нажимает на Item2, он отображается Item3 в подробном представлении (то есть, если не сказатьправильные данные)

Ниже приведен код, который выполняется при нажатии элемента в представлении списка;

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    // Navigation logic -- create and push a new view controller

    if(bookdetailCustom == nil)
        bookdetailCustom = [[BookDetailCustom alloc] initWithNibName:@"BookDetailCustom" bundle:[NSBundle mainBundle]];

    //aBook = [retrievedArray objectAtIndex:indexPath.row];

    bookdetailCustom.selectedIndex = indexPath.row;

    [self.navigationController pushViewController:bookdetailCustom animated:YES];
    [bookdetailCustom release];
    bookdetailCustom = nil;
}

Вот как выглядит searchTabkleView

- (void) searchTableView {
    NSString *searchText = searchBar.text;
    NSMutableArray *searchArray = [[NSMutableArray alloc] init];

    for (int i=0;i<[retrievedArray count];i++)
    {
        Stock *aBookTemp = [retrievedArray objectAtIndex:i];
        NSString *temp = [aBookTemp valueForKey:@"BookName"];
        [searchArray addObject:temp];
    }

    for (NSString *sTemp in searchArray)
    {
        NSRange titleResultsRange = [sTemp rangeOfString:searchText options:NSCaseInsensitiveSearch];

        if (titleResultsRange.length > 0)
            [copyListOfItems addObject:sTemp];
    }

    [searchArray release];
    searchArray = nil;
}

Пожалуйстапредложить несколько подходящих исправлений.

Ответы [ 5 ]

5 голосов
/ 27 февраля 2011

Из того, что вы опубликовали, каждый retrievedArray указывает на один и тот же объект NSMutableArray.Так что нет никаких отдельных массивов для синхронизации, это все тот же массив.

Однако NSMutableArray не является поточно-ориентированным;вещи могут взорваться, если один поток изменяет это, в то время как другой читает это.Простого изменения свойства с неатомного на атомарное недостаточно, поскольку оно охватывает только выборку самого объекта массива, а не последующие вызовы методов для доступа к элементам внутри массива.Однако я не думаю, что это является причиной вашей основной проблемы, и решение этой проблемы должно устранить проблему безопасности потоков.

Я предполагаю, что последовательность событий выглядит примерно так:

  1. Представление «Список» отображает набор результатов, который включает A в индексе N.
  2. Пользователь что-то печатает.Синтаксический анализатор XML начинает обновлять общий массив постепенно.Представление «Список» еще не обновлено.
  3. Пользователь касается элемента с индексом N в представлении «Список».В представлении списка указывается подробное представление для отображения элемента с индексом N.
  4. Подробное представление извлекает элемент с индексом N из общего массива, но из-за обновления, начатого на шаге 2, индекс N теперь содержит B.Что отображается в подробном представлении.
  5. В какой-то момент анализ XML завершается, и теперь список обновляется.

Это также должно быть возможно, если загрузка и анализ выполняется из веб-службы.достаточно медленный, чтобы на шаге 4 просто произошел сбой с NSRangeException.

Одним решением было бы, чтобы каждый элемент в Списке содержал фактический объект результата и передавал его в подробное представление, а не только в индекс.В этом случае вы можете полностью избавиться от общего массива, если единственными потребителями являются List и Detail или любые другие потребители могут быть изменены таким же образом, чтобы принимать объекты вместо индексов.Другим вариантом было бы, чтобы синтаксический анализатор накапливал результаты в частном массиве и обновлял общий массив сразу, прежде чем сигнализировать представлению списка об обновлении самого себя;Существует небольшая вероятность расхождения во времени между обновлением в фоновом потоке и вызовом метода в основном потоке, но окно, вероятно, немного меньше.

Или я могу быть совершенно не правя думаю о том, как работает обновление, и в этом случае вы должны предоставить более подробную информацию.

2 голосов
/ 27 февраля 2011

Первоначально я предлагал вам удалить ключевое слово nonatomic из декларации вашего свойства.По умолчанию используется атомарное (нет настройки atomic, достаточно пропустить nonatomic), что обеспечит вам безопасность потоков, обернув синтезированный сеттер в блок @synchronize.

К сожалению, многие людинаучились просто вставлять nonatomic во весь код, не понимая его.Я всегда думал, что это происходит от копирования / вставки из примера кода Apple - они часто используют его для вещей, связанных с пользовательским интерфейсом - помните, что UIKit не является потокобезопасным.

Аноми указал в своем/ ее ответ, что это не так, скорее всего, потому что вы изменяете изменяемый массив из разных потоков. Это звучит как правильный ответ для меня - я бы удалил свой ответ, но я оставлю его здесь, так как думаю, что мои комментарии чего-то стоят (но не на 100% относятся к вашей проблеме).

1 голос
/ 11 марта 2011

Я понимаю, что вы потратили много времени и усилий, чтобы исправить это, и решение Anomie является оптимальным для этого. Но, возможно, будет проще реализовать другой подход.

Например, вы можете заставить анализатор обрабатывать данные и передавать их в хранилище базовых данных. Список, в свою очередь, будет подпитываться NSFetchedResultsController. Контроллер автоматически заботится о содержимом таблицы и любой синхронизации, которая должна быть выполнена.

Стоит попробовать, и я надеюсь, что это поможет.

0 голосов
/ 05 марта 2011

Проблема в том, что на retrievedArray ссылаются два потока. Удалите все ссылки на retrievedArray из вашего кода синтаксического анализа XML и измените его только в главном потоке.

Вот процесс:

  1. Измените parseXMLFile:, чтобы создать новый массив: parsedArray = [NSMutableArray array]
  2. Измените parser:didEndElement:, чтобы добавить к этому новому массиву: [parsedArray addObject:aBook]
  3. В parser:didEndDocument: передать ваш новый массив в основной поток:

    [delegate performSelectorOnMainThread: @selector(updateRetrievedArray:)
                               withObject: parsedArray
                            waitUntilDone: NO];
    
  4. updateRetrievedArray: выполнение в главном потоке будет кодом, отвечающим за обновление retrievedArray - таким образом, только один поток изменяет этот объект:

    - (void) updateRetrievedArray: (NSArray *)parsedArray {
        [retrievedArray setArray:parsedArray];
        [self parseDidComplete_srch_book]; // Be sure to call [tableView reloadData]
    }
    
0 голосов
/ 02 марта 2011

Попробуйте использовать NSRecursiveLock в методах доступа для массива.

См. Документацию NSRecursiveLock . Из обзора:

NSRecursiveLock определяет блокировку, которая может быть получена несколько раз одним и тем же потоком, не вызывая взаимоблокировку, ситуацию, когда поток постоянно блокируется, ожидая, что он снимет блокировку. Хотя поток блокировки имеет одну или несколько блокировок, все остальные потоки не имеют доступа к коду, защищенному блокировкой.

Пример кода CoreVideo содержит примеры его правильного использования.

...