集約関数と1行のグループ化を使用したCoreDataのクエリ

CoreDataを使用するほとんどの人は、遅かれ早かれ集計関数グループ化を使用してクエリを作成する必要があります 。 ただし、CoreDataのこのようなクエリの構文は理解が難しく、不合理に冗長です。



クエリデザイナーを使用してたとえば、次の要求を行うことができます

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





このクエリはこれと同等です:

CoreDataでのリクエスト
 NSFetchRequest *fetchRequest = [[ALFetchRequest alloc] init]; fetchRequest.managedObjectContext = managedObjectContext; NSString *entityName = @"Product"; NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:managedObjectContext]; [fetchRequest setEntity:entity]; [fetchRequest setIncludesPendingChanges:YES]; // sum amount NSExpression *fieldExp1 = [NSExpression expressionForKeyPath:@"amount"]; NSExpression *agrExp1 = [NSExpression expressionForFunction:agr arguments:@[fieldExp1]]; NSExpressionDescription *resultDescription1 = [[NSExpressionDescription alloc] init]; NSString *resultName1 = @"sumAmount"; [resultDescription1 setName:resultName1]; [resultDescription1 setExpression:agrExp1]; [resultDescription1 setExpressionResultType:NSInteger64AttributeType]; // average price NSExpression *fieldExp2 = [NSExpression expressionForKeyPath:@"price"]; NSExpression *agrExp2 = [NSExpression expressionForFunction:agr arguments:@[fieldExp1]]; NSExpressionDescription *resultDescription2 = [[NSExpressionDescription alloc] init]; NSString *resultName2 = @"sumAmount"; [resultDescription2 setName:resultName2]; [resultDescription2 setExpression:agrExp2]; [resultDescription2 setExpressionResultType:NSInteger64AttributeType]; // country NSDictionary *availableKeys = [entity attributesByName]; NSAttributeDescription *country = [availableKeys valueForKey:@"country"]; fetch.propertiesToFetch = [NSArray arrayWithObjects:country, resultDescription1, resultDescription2, nil]; fetch.propertiesToGroupBy = [NSArray arrayWithObject:country]; fetch.resultType = NSDictionaryResultType; 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;
      
      









この記事では、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を取得するために使用されます。 例えば

 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 omによって管理されるオブジェクトを取得できます。

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





これにより、UITableViewDataSourceプロトコルとNSFetchedResultsControllerのデリゲートを実装するための退屈なコードを書く必要がなくなります。 collectionViewDataSourceメソッドを使用して、 UICollectionViewDataSourceに対して同様のオブジェクトを取得できます。



オブジェクトの作成と削除



次の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]; //  
      
      





最後に注意する点は、ManagedObjectのエンティティ名が クラス名と一致しない場合、 + entityNameメソッドをオーバーロードする必要があることです(当然、対応するカテゴリでこれを行う必要があります)。

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









例でライブラリ使用することの利益を示しましょう。 ライブラリをダウンロードして解凍した後、依存関係をインストールする必要があります。

 cd /ユーザー/あなた/ダウンロード/ ALCoreDataManager-master /例
ポッドインストール




ストーリーボードでは、すべてが非常に簡単です。



最初のTableViewControllerには、すべての製品のリストが表示されます。 2番目には、選択した製品に関する情報が表示され、そこで編集できます。



テーブルは、前述の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; }
      
      





信じられないかもしれませんが、これがTableViewのすべてのコードです。



[ 追加 ]をクリックして、次のように要素を作成します。

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





統計をクリックすると、いくつかの統計情報が表示されます。





統計のタイプを選択すると、コードが実行されます。

 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; // }
      
      





これが、アグリゲーター関数を使用したコード全体です(比較のために- 集約関数を使用した要求(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; }
      
      





ほとんどすべてのビルダーコードは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; }
      
      





これは、NSFetchRequestの単なる構文上の砂糖であると想定できます。 明らかに、オーバーヘッドはほとんどゼロです。 テストでもう少し例を見つけることができます。



これで、私は物語を終わらせようと急いでいます。 ご清聴ありがとうございました。



All Articles