Как я могу загрузить данные для NSTableView, не блокируя интерфейс?

Я инициализирую простой интерфейс с NSTableView, привязанным к контроллеру массива (который управляет массивом словарей). Я хочу загрузить содержимое для массива в фоновом режиме (это очень трудоемкий процесс), обновляя представление таблицы каждые 100 или 1000 элементов. Идея в том, что интерфейс доступен и отзывчив. Я не могу понять, как после этого запускать обновление/обновление. Таблица остается пустой. Может ли кто-нибудь предложить указатели?

Мой текущий подход:

// In init for my app controller. This seems to work well, but I've tried other methods here.
[self performSelectorInBackground:@selector(loadTable) withObject:nil];


- (void)loadTable {
  tracks = [[NSMutableArray alloc] initWithCapacity:[masters count]];

  // ... create each object one-by-one. Add it to tracks.
  for (... in ...) {
    [tracks addObject:newObject];
  }

  // Now I don't know what to do next. The table remains empty. 
  // Things I've tried (though possibly not in all combinations with the
  // method above):
  // 1. With a suitably-defined reloadData method, which just reloads
  //   the table view and sets needs display.
  [self performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:YES];

  // 2. Reload directly.
  [tv reloadData];
  [tv setNeedsDisplay];
}

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


person marqueed    schedule 22.11.2010    source источник


Ответы (2)


У вас есть столбцы таблицы (я полагаю, вы имели в виду), привязанные к контроллеру массива, поэтому табличное представление получает свои данные. Табличное представление вполне может запрашивать обновленные массивы, но оно запрашивает контроллер массива, который не знает, что что-то изменилось.

Контроллер массива не будет просто разворачиваться и запрашивать у вас свежие данные; это означало бы, что он существует исключительно для того, чтобы вам было сложнее привязать табличное представление к вашему массиву, а это не так. Это контроллер; его работа состоит в том, чтобы владеть (копией) массивом и поддерживать его порядок и выбор пользователем некоторого подмножества его объектов.

Поэтому вам нужен контроллер массива, чтобы узнать, когда вы добавляете элементы в свой массив. Лучший способ сделать это — привязать contentArray контроллера массива к свойству вашего контроллера и обновить это свойство в соответствии с KVO.

Это значит:

  1. Создайте изменяемый массив в методе init. (И, конечно же, выпустить его в dealloc.)
  2. Реализуйте методы доступа к массиву, а также addTracksObject: и removeTracksObject: (технически методы, поэтому KVO будет игнорировать их для свойства массива) для вашего удобства.
  3. Чтобы добавить трек, отправьте себе addTracksObject: сообщение. Вы должны ответить на это, отправив себе сообщение insertObject:inTracksAtIndex:[self countOfTracks] для индекса, если вы не хотите сделать инсорт), и вы должны ответить на insertObject:inTracksAtIndex:, отправив вашему массиву tracks сообщение insertObject:atIndex:.

Как я уже упоминал, KVO будет игнорировать addFooObject: и removeFooObject:, когда foo является свойством NSArray, учитывая только эти методы доступа к свойству NSSet, поэтому вам нужно реализовать их поверх insertObject:inFooAtIndex: и removeObjectFromFooAtIndex:, потому что эти являются методами доступа к массиву, а значит КВО на них отреагирует.

Шаг 3, как я только что описал, будет довольно медленным, потому что он заставит контроллер массива повторно получить ваше свойство, а табличное представление повторно получить arrangedObjects контроллера массива по крайней мере один раз для каждой добавляемой вами строки.

Таким образом, вы должны поддерживать свое поведение пакетного добавления с помощью этого альтернативного шага 3:

  • Реализуйте insertTracks:atIndexes: и передайте ему массив из одного пакета (например, 100 или 1000) треков и набора индексов, сформированного [NSIndexSet indexSetWithRange:(NSRange){ [self countOfTracks], countOfBatch }]. Вам также потребуется реализовать removeTracksAtIndexes:, только потому, что KVO будет игнорировать каждый метод вставки, если у вас также нет его аналога.

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

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

person Peter Hosey    schedule 22.11.2010

Вам нужно вызвать setNeedsDisplay:YES в табличном представлении в основном потоке. Не вызывайте его из фонового потока. Все вызовы пользовательского интерфейса Cocoa должны выполняться в основном потоке, иначе могут произойти странные вещи.

person lucius    schedule 22.11.2010