iOS, Удаленный поиск серверов с помощью RestKit

Я работаю над приложением, в котором хочу выполнить удаленный поиск на сервере. Я хочу, чтобы RestKit сохранял полученные данные в базе данных. Сначала я выполняю локальный поиск (который в настоящее время работает), затем я хочу выполнить удаленный поиск, а затем обновить табличное представление новыми результатами.

У меня две проблемы: 1. как должно выглядеть мое сопоставление и 2. json возвращает массив с двумя разными типами объектов.

URL-адрес выглядит следующим образом:

search.json?search=[search string]

Возвращаемый JSON выглядит следующим образом:

[
  {
    "event": {
      "id": 2,
      [...]
  },
  {
    "news": {
      "id": 16,
      [...]
  }

Где событие и новость - это два вида объектов.

В моем приложении у меня есть три модели: Post (абстрактный объект и суперкласс), NewsPost (подкласс Post) и Event (подкласс Post).

Мои сопоставления выглядят так:

RKManagedObjectMapping* newsMapping = [RKManagedObjectMapping mappingForClass:[NewsPost class] inManagedObjectStore:objectManager.objectStore];   
newsMapping.primaryKeyAttribute = @"newsId";
newsMapping.rootKeyPath = @"news";
[newsMapping mapKeyPath:@"id" toAttribute:@"newsId"];

RKManagedObjectMapping *eventMapping = [RKManagedObjectMapping mappingForClass:[CalendarEvent class] inManagedObjectStore:objectManager.objectStore];
eventMapping.primaryKeyAttribute = @"calendarId";
eventMapping.rootKeyPath = @"calendars";
[eventMapping mapKeyPath:@"id" toAttribute:@"calendarId"];

// These two works. 
[objectManager.mappingProvider setObjectMapping:newsMapping forResourcePathPattern:@"/package_components/1/news"];
[objectManager.mappingProvider setObjectMapping:eventMapping forResourcePathPattern:@"/package_components/1/calendars"];

// I don't know how these should look/work. 
// Since the search word can change
[objectManager.mappingProvider setObjectMapping:eventMapping forResourcePathPattern:@"/package_components/1/search\\.json?search="];
[objectManager.mappingProvider setObjectMapping:newsMapping forResourcePathPattern:@"/package_components/1/search\\.json?search="];

Мой поисковый код выглядит так (локальный поиск работает):

- (void)setUpSearch
{
    if (self.searchField.text != nil) {

        [self.posts removeAllObjects];
        [self.events removeAllObjects];
        [self.news removeAllObjects];

        // Search predicates.
        // Performs local search.
        NSPredicate *contactNamePredicate = [NSPredicate predicateWithFormat:@"contactName contains[cd] %@", self.searchField.text];
        NSPredicate *contactDepartmentPredicate = [NSPredicate predicateWithFormat:@"contactDepartment contains[cd] %@", self.searchField.text];
        [...]

        NSArray *predicatesArray = [NSArray arrayWithObjects:contactNamePredicate, contactDepartmentPredicate, contactEmailPredicate, contactPhonePredicate, linkPredicate, titlePredicate, nil];

        NSPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:predicatesArray];

        self.posts = [[Post findAllWithPredicate:predicate] mutableCopy];

        if (self.posts.count != 0) {
            self.noResultsLabel.hidden = YES;
            for (int i = 0; i < self.posts.count; i++) {
                Post * post = [self.posts objectAtIndex:i];
                if (post.calendarEvent == YES) {
                    [self.events addObject:post];
                } else {
                    [self.news addObject:post];
                }
            }
        } 

        // reload the table view
        [self.tableView reloadData];

        [self performRemoteSearch];
    }
}

- (void)search
{    
    [self setUpSearch];
    [self hideKeyboard];
    [self performRemoteSearch];
}


- (void)performRemoteSearch
{
    // Should load the objects from JSON    
    // Note that the searchPath can vary depending on search text. 
    NSString *searchPath = [NSString stringWithFormat:@"/package_components/1/search.json?search=%@", self.searchField.text];
    RKObjectManager *objectManager = [RKObjectManager sharedManager];
    [objectManager loadObjectsAtResourcePath:searchPath delegate:self];
}

- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects
{
    // This never gets called. 

    // Should update my arrays and then update the tableview, but it never gets called. 
    // Instead I get Error Domain=org.restkit.RestKit.ErrorDomain Code=1001 "Could not find an object mapping for keyPath: ''
}

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


person Anders    schedule 09.08.2012    source источник
comment
Привет, я думаю попробовать этот вопрос. 1. Можно ли каким-либо образом изменить формат JSON? Я хочу знать, можете ли вы добавить путь к корневому ключу для каждого из результатов (это значительно упростит его). 2. Вы используете NSManagedObject для новостей и событий. Собираетесь ли вы сохранить сопоставление в Coredata при поиске пользователя?   -  person Damon Aw    schedule 15.08.2012
comment
Привет, да, если нужно, я могу изменить JSON. И да, я хочу кэшировать и сохранять результаты локально, когда пользователи выполняют поиск.   -  person Anders    schedule 15.08.2012
comment
Дайте мне знать, если ответ работает для вас.   -  person Damon Aw    schedule 17.08.2012


Ответы (2)


Я раньше не использовал Managed Objects, но первое, что нужно сделать здесь, это активировать журнал ресткита по сопоставлению объектов и сетевому запросу, чтобы вы могли проверить, что ресткит получает с сервера и как работает сопоставление.

//This can be added in your app delegate
RKLogConfigureByName("RestKit/Network", RKLogLevelDebug);
RKLogConfigureByName("RestKit/ObjectMapping", RKLogLevelTrace);

Во-вторых, в соответствии с вашим JSON и тем, что ваш путь поиска меняется, я думаю, что лучше использовать сопоставление для ключевого пути вместо шаблона пути к ресурсу. Поэтому вы должны попытаться отобразить по ключу, как в этом примере:

RKObjectMapping* articleMapping = [RKObjectMapping mappingForClass:[Article class]];
[articleMapping mapKeyPath:@"title" toAttribute:@"title"];
[articleMapping mapKeyPath:@"body" toAttribute:@"body"];
[articleMapping mapKeyPath:@"author" toAttribute:@"author"];
[articleMapping mapKeyPath:@"publication_date" toAttribute:@"publicationDate"];

[[RKObjectManager sharedManager].mappingProvider setMapping:articleMapping forKeyPath:@"articles"];

А затем загрузите свои данные, например:

- (void)loadArticles {
    [[RKObjectManager sharedManager] loadObjectsAtResourcePath:@"/articles" delegate:self];
}

Другой способ сделать это — отобразить по объекту, поэтому RestKit определяет тип объекта и выполняет сопоставление, а вы делаете запрос по любому пути.

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

person clopez    schedule 14.08.2012
comment
Спасибо, завтра буду тестить. - person Anders; 15.08.2012

Я никогда раньше не пытался ответить на вопрос с наградой, позвольте мне попытаться дать полезный ответ из какой-то недавней работы =)

<сильный>1. как должно выглядеть мое сопоставление

Судя по твоему коду, все выглядит прекрасно. Есть ли вложенность объектов? Вам нужно сериализовать для отправки обратно на сервер?

<сильный>2. json возвращает массив с двумя разными типами объектов.

Ваши атрибуты одинаковы (например, у события есть название, у события есть дата) без сюрпризов? Если нет, вы должны использовать dynamic nesting.

Если путь к ресурсу (то есть ваш путь поиска) получает коллекцию с разными объектами (ваш случай), вы должны использовать dynamic object mapping для загрузки объектов.

Поскольку вы можете редактировать структуру JSON, все может быть проще, используя RestKit.

- Убедитесь, что в JSON есть root_key_path для двух разных типов объектов.

Из старого эксперимента и некоторых поисков в Google RestKit может правильно сопоставлять вывод json с различными объектами, если у них есть правильные пути rootKeyPath. Результирующий JSON должен иметь грубую структуру, например:

{
  "news" : [
    {
      "id" : 1,
      "title" : "Mohawk guy quits"
    },
    {
      "id" : 2,
      "title" : "Obama gets mohawk"
    }
  ],
  "events" : [
    {
      "id" : 1,
      "name" : "testing"
    },
    {
      "id" : 2,
      "name" : "testing again"
    }
  ]
}

Я не могу быть уверен на 100%, что вышесказанное верно. Вы можете поэкспериментировать, заставив свой API возвращать только новости, если он работает, а затем добавить в микс данные о событиях.

- Загрузить объекты с сервера

// Make a NS dictionary and use stringByAppendingQueryParameters

NSDictionary *searchParams = [NSDictionary dictionaryWithKeysAndObjects:
                                @"query",@"myQuery", 
                                @"location",@"1.394168,103.895473",
                                nil];

[[RKObjectManager sharedManager] loadObjectsAtResourcePath:[@"/path/to/resource.json"  stringByAppendingQueryParameters:searchParams] delegate:objectLoaderDelegate];

- Управляйте "настоящим" поиском в вашем делегате objectLoader

Если это сработало, объекты должны быть сопоставлены с вашими объектами Coredata. Вы можете выполнить локальный поиск, используя метод NSPredicate, который вы указали выше.

Я предпочитаю шаблон проектирования, в котором RestKit использует loadObjects... для получения данных с сервера и их сопоставления, а остальная обработка выполняется локально. Это разделение делает вещи более похожими на приложения. Вы можете выполнить другую форму манипуляции, используя NSPredicates.

- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects { 
  // Do some processing here on the array of returned objects or cede control to a method that 
  // you've built for the search, like the above method.
}

Например, если вариант использования поиска — рестораны поблизости, вероятно, имеет смысл загрузить все рестораны в пределах текущей широты и долготы, а затем выполнить локальную фильтрацию по имени с помощью Coredata. Ваш сервер понравится вам.

Дайте мне знать, и я постараюсь улучшить ответ.

person Damon Aw    schedule 15.08.2012
comment
Спасибо за ваш ответ, и извините за мой поздний ответ. Я действительно заставил его работать, используя ответ @clopez с некоторыми незначительными изменениями. - person Anders; 20.08.2012