Почти всем, кто использует 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/
Добавить комментарий