Запросы в CoreData с агрегатными функциями и группировкой в одну строку

от автора

Почти всем, кто использует CoreData, рано или поздно приходится создавать запросы с агрегатными функциями и группировками. Однако, синтаксис таких запросов в CoreData сложен для понимания и неоправданно многословен.

Используя конструктор запросов мы можем, например, сделать вот такой запрос:

NSDictionary *productTotalSumAndAveragePriceGroupedByCountries =  [[[[[Product all ] aggregatedBy:@[                  @[kAggregateSum, @"amount"],                  @[kAggregatorAverage, @"price"]] ] groupedBy:@[@"country"] ] having:predicate ] execute]; 


В данной статье мне бы хотелось рассказать о небольшой библиотеке для работы с CoreData, которая появилась как обобщение моего скромного опыта разработки под iOS. Библиотека доступна в cocoapods.

Ядром библиотеки является синглтон класс ALCoreDataManager, который отвечает за инициализацию и подключение стэка CoreData и возвращает NSManagedObjectContext. Замечу, что это совершенно ординарная вещь и аналогов в этом плане — очень много. Все вкусности содержатся в категории ALFetchRequest+QueryBuilder и фактори классе ALManagedObjectFactory. Но обо всем по порядку.

Возможности библиотеки

Будем полагать, что модель Product определена так:

@interface Product : NSManagedObject @property (nonatomic, retain) NSString *title; @property (nonatomic, retain) NSNumber *price; @property (nonatomic, retain) NSNumber *amount; @property (nonatomic, retain) NSString *country; @end 

Запросы

Используя конструктор запросов, мы можем формировать, например, следующие запросы:

NSArray *allProducts =  [[Product all] execute];  NSArray *productsFilteredWithPredicate =  [[[Product all] where:predicate] execute];  NSArray *singleProduct =  [[[[Product all] where:predicate] limit:1] execute];  NSArray *onlyDistinctProductTitles =  [[[[Product all] properties:@[@"title"]] distinct] execute];  NSArray *countProducts = [[[[Product all] where:predicate] count] execute]; // NSInteger count = [[countProducts firstObject] integerValue];  NSArray *productsOrderedByTitleAndPrice =  [[[Product all ] orderedBy:@[               @[@"title", kOrderDESC],               @[@"price", kOrderASC],               @[@"amount"]] ] execute];  NSArray *totalAmountAndAveragePriceForProducts =  [[[[[Product all ] aggregatedBy:@[                  @[kAggregateSum, @"amount"],                  @[kAggregateAverage, @"price"]] ] groupedBy:@[@"country"] ] having:predicate ] execute]; 

Метод execute используется непосредственно для выполнения запроса. Для получения сформированного NSFetchRequest предназначен метод request. Например,

NSFetchRequest *request = [[[Product all] orderedBy:@[@"title", @"price"]] request]; NSManagedObjectContext *context = [ALCoreDataManager defaultManager].managedObjectContext; NSFetchedResultsController *controller = [[NSFetchedResultsController alloc] initWithFetchRequest:request                                     managedObjectContext:context                                       sectionNameKeyPath:nil                                                cacheName:nil]; [controller performFetch:nil]; 

UITableViewDataSource

По имеющемуся запросу можно получить обьект, реализующий протокол UITableViewDataSource и управлеяемый NSFetchedResultsControllerом:

ALTableViewDataSource *dataSource =         [[[Product all] orderedBy:@[kTitle, kPrice]] tableViewDataSource];  self.dataSource.tableView = self.tableView; 

Что избавит нас от написания скучного кода для реализации протокола UITableViewDataSource и делегата для NSFetchedResultsController-а. Аналогичный объект можно получить для UICollectionViewDataSource используя метод collectionViewDataSource.

Создание и удаление объектов

Создание и удаление обьектов возможно с использованием такого API:

Product *a = [Product create]; Product *b = [Product createWithDictionary:@{ @"title" : @"best product" }];  // или используя Factory-класс NSManagedObjectContext *context = [ALCoreDataManager defaultManager].managedObjectContext; ALManagedObjectFactory *factory = [[ALManagedObjectFactory alloc] initWithManagedObjectContext:context];  Product *c = [Product createWithDictionary:nil                                usingFactory:factory]; c.title = @"best product 2";  Product *d = [Product createWithDictionary:@{ @"title" : @"best product 3", @"price" : @(100) }                                usingFactory:factory];  [d remove]; // удаляем объект 

Последнее, что нужно отметить — вы обязаны перегрузить метод +entityName, если Entity Name некоторого ManagedObject-а не совпадает с его Class Name (естественно, сделать это необходимо в соответствующей категории).

@implementation Product + (NSString*)entityName {     return @"AnItem"; } @end 

Example

Продемонстрируем профит от использования библиотеки на примере. После скачивания и разархивирования библиотеки необходимо установить зависимости:

 cd /Users/you/Downloads/ALCoreDataManager-master/Example pod install 

В Storyboard-е все достаточно просто:

В первом TableViewController-е отображается список всех Product-ов; во втором отображается информация по выбранному Product-у, которую можно там же отредактировать.

Заполнение таблицы происходит с помощью упоминавшегося ранее ALTableViewDataSource-а:

- (void)viewDidLoad {     [super viewDidLoad];          self.dataSource = [[[Product all] orderedBy:@[kTitle, kPrice]] tableViewDataSource];     __weak typeof(self) weakSelf = self;     self.dataSource.cellConfigurationBlock = ^(UITableViewCell *cell, NSIndexPath *indexPath){         [weakSelf configureCell:cell atIndexPath:indexPath];     };     self.dataSource.reuseIdentifierBlock = ^(NSIndexPath *indexPath){         return TableViewCellReuseIdentifier;     };     self.dataSource.tableView = self.tableView; } 

По нажатию на Add, создаем элемент, сказав:

[Product createWithFields:@{                             kTitle : title,                             kPrice : @(0),                             kAmount : @(0)                             }              usingFactory:[ALManagedObjectFactory defaultFactory]]; 

Покажем некоторую статистическую информацию по нажатию на Statistics:

После выбора типа статистики отрабатывает код:

ALFetchRequest *request = nil; switch (st) {     case ALStatsTypeTotalAmount:         request = [[Product all] aggregatedBy:@[@[kAggregatorSum, kAmount]]];         break;     case ALStatsTypeMedianPrice:         request = [[Product all] aggregatedBy:@[@[kAggregatorAverage, kPrice]]];         break;     default:         break; } NSArray *result = [request execute]; // request будет иметь тип NSDictionaryResultType NSDictionary *d = [result firstObject]; // Примерный результат: // { //    sumAmount = 1473; // } 

Вот и весь код с агрегаторными функциями (для сравнения — request с агрегатной функцией (stackoverflow)).

После выполнения получим такой AlertView:

Как это работает

Формирование запроса начинается с вызова:

+ (ALFetchRequest*)allInManagedObjectContext:(NSManagedObjectContext*)managedObjectContext; + (ALFetchRequest*)all; // внутри вызов allInManagedObjectContext с defaultContext 

То есть просто приводит к созданию NSFetchRequest-а:

+ (ALFetchRequest*)allInManagedObjectContext:(NSManagedObjectContext*)managedObjectContext { 	ALFetchRequest *fetchRequest = [[ALFetchRequest alloc] init]; 	fetchRequest.managedObjectContext = managedObjectContext; 	NSEntityDescription *entity = [self entityDescriptionWithMangedObjectContext:managedObjectContext];  	[fetchRequest setEntity:entity]; 	[fetchRequest setIncludesPendingChanges:YES];  	return fetchRequest; } 

Практически весь код builder-а находится в ALFetchRequest+QueryBuilder.m.

Каждый из вызовов вида

[[[Product all] orderedBy:@[kTitle, kPrice]] limit:1]; 

Просто приводит к добавлению необходимых настроек в созданный NSFetchRequest, например:

- (ALFetchRequest*)limit:(NSInteger)limit { 	self.fetchLimit = limit; 	return self; } 

Мотоды execute и request:

- (NSArray*)execute { 	NSError *error; 	NSManagedObjectContext *managedObjectContext = self.managedObjectContext; 	NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:self error:&error]; 	if (!fetchedObjects || error) { 		NSLog(@"Error: Execution of the fetchRequest: %@, Failed with Description: %@",self,error); 	} 	return fetchedObjects; } - (NSFetchRequest *)request { 	return (NSFetchRequest*)self; } 

Можно считать, что это просто syntactic sugar для NSFetchRequest. Очевидно, overhead практически равен нулю. Чуть больше примеров можно найти в тестах.

На этом повествование спешу закончить. Благодарю за внимание.

ссылка на оригинал статьи http://habrahabr.ru/post/265319/


Комментарии

Добавить комментарий

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