NSFetchedResultsController的簡單封裝 - UITableView與CoreData的完美結合

NSFetchedResultsController的簡單封裝 - UITableView與CoreData的完美結合

本文將簡單分析NSFetchedResultsController這個控制器類的用法和開源控件DEFetchRC的使用方法,文中用於講解的代碼片斷,並不能直接粘貼使用,具體細節能夠下載完整代碼git

若是您在閱讀本文時,對CoreData尚未任何基礎概念,這裏給您推薦一套來自objcio.cn的CoreData系列教程,相信能對您有所幫助。github

基本概念MVC與MVVC

從傳統意義的MVC到MVVC的轉變,相信你們都厭煩了術語套話,這些設計模式概念用天然語言描述顯得十分抽象,咱們今天用Cocoa的類來簡單描述一下這兩種模式。數據庫

傳統MVC Model - View - Controller

剛入門Cocoa時,咱們接觸到的最基本的類,就是UIViewController和UIView類,他們對應的MVC模型中的Controller和View,Model(數據模型)則能夠是CoreData的數據,網絡請求的數據,或者其餘一些格式的數據,MVC的運做模式就是Controller將Model內容填入View,View響應交互讓Controller處理數據而且修改View。segmentfault

圖片描述

MVVC Model - View - Controller - ViewModel

通過實戰咱們發現,Controller的代碼量龐大不堪,一方面操做數據,一方面操做View,臃腫不堪,難以維護。因此MVVC模型應運而生,在MVC的基礎上,加入了ViewModel概念,這個(些)ViewModel能夠做爲Controller的屬性,剝離Controller中複雜的任務,例如:設計模式

  1. 替Controller實現TableView和CollectionView的回調。網絡

  2. 剝離數據庫操做app

  3. 剝離網絡請求模塊化

這樣讓Controller專一於處理View佈局、交互和動畫,把任務剝離,下降總體項目的耦合性。佈局

NSFetchedResultsController

基於高度模塊化和低耦合性的設計趨勢,NSFetchedResultsController類應運而生。
你可能還不知道這個類能夠完成什麼工做,頭文件中如此描述:fetch

This class is intended to efficiently manage the results returned from a Core Data fetch request.
這個類用於高效地響應CoreData數據變化

本文的Demo代碼見zsy78191/DEFetchRC
初始化代碼,這段代碼摘自蘋果官方模版,咱們自建DETableViewFetchRC類用於綁定,這裏在DETableViewFetchRC類中實現NSFetchedResultsController相關方法,使用的參數包括

  1. self.entityName 實體名稱

  2. self.managedContext CoreData的上下文

  3. self.predicate 檢索謂語

  4. self.sortKey 排序鍵

  5. self.ascending 排序升序或降序

  6. _fetchController.delegate NSFetchedResultsControllerDelegate委託

@property (nonatomic, strong) NSFetchedResultsController* fetchController;

- (NSFetchedResultsController *)fetchController
{
    if (_fetchController != nil) {
        return _fetchController;
    }
    
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    // Edit the entity name as appropriate.
    NSEntityDescription *entity = [NSEntityDescription entityForName:self.entityName inManagedObjectContext:self.managedContext];
    [fetchRequest setEntity:entity];
    
    // Set the batch size to a suitable number.
    [fetchRequest setFetchBatchSize:20];
    [fetchRequest setPredicate:self.predicate];
    
    // Edit the sort key as appropriate.
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:self.sortKey ascending:self.ascending];
    NSArray *sortDescriptors = @[sortDescriptor];
    
    [fetchRequest setSortDescriptors:sortDescriptors];
    
    // Edit the section name key path and cache name if appropriate.
    // nil for section name key path means "no sections".
    _fetchController= [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedContext sectionNameKeyPath:nil cacheName:nil];
    _fetchController.delegate = self;
    
    NSError *error = nil;
    if (![_fetchController performFetch:&error]) {
        // Replace this implementation with code to handle the error appropriately.
        // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
        ////LOGMARK(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
    
    return _fetchController;
}

綁定好的NSFetchedResultsController經過KVO觀察CoreData上下文變化,並通知咱們修改View,具體實現以下:

綁定tableView的DataSource

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return [[self.fetchController sections] count];
}

注:NSFetchedResultsController是支持TableView的Section功能的,咱們在這個例子中國年並無使用,讀者能夠根據須要進行修改。

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchController sections][section];
    return [sectionInfo numberOfObjects];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    if([self checkDataSourceCanResponse:@selector(tableView:cellAtIndexPath:withData:)])
    {
        NSManagedObject *data = [self.fetchController objectAtIndexPath:indexPath];
         
        //在這裏處理Cell內容
    }
    return nil;
}

綁定增刪改

這裏NSFetchedResultsController提供了NSFetchedResultsControllerDelegate協議,用於綁定Model和View,以下。

#pragma mark - fetch


- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
    [self.tableView beginUpdates];
}

- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
           atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
    switch(type) {
        case NSFetchedResultsChangeInsert:
            [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
            
        case NSFetchedResultsChangeDelete:
            [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationLeft];
            break;
            
        default:
            return;
    }
}

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(NSManagedObject*)anObject
       atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath
{
    UITableView *tableView = self.tableView;
    switch(type) {
        case NSFetchedResultsChangeInsert:
            [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
            
        case NSFetchedResultsChangeDelete:
            [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft];
            break;
            
        case NSFetchedResultsChangeUpdate:
            [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
            
        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
            [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
    }
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    [self.tableView endUpdates];
}

至此,NSFetchedResultsController的基本使用方法都介紹了,接下來介紹一下我作的一個對NSFetchedResultsController的簡單封裝,zsy78191/DEFetchRC

DEFetchRC

一個NSFetchedResultsController的簡單封裝

DETableViewFetchRC將自動綁定CoreData的ManageContext和TableView,綁定好之後,數據的任何變更,包括增刪改查,都會自動調用對應Cell的更新,不須要本身寫任何代碼作通知掛鉤等。

Demo中使用Apple的CoreData模版,CoreData的Context在AppDelegate中。

- (NSManagedObjectContext*)managedObjectContext
{
    AppDelegate* delegate = [UIApplication sharedApplication].delegate;
    return delegate.managedObjectContext;
}

DETableViewFetchRC初始化

@property (nonatomic, strong) DETableViewFetchRC* tableFetchResultController;

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self.managedObjectContext setUndoManager:[[NSUndoManager alloc] init]];
    
   
    self.tableFetchResultController = [DETableViewFetchRC fetchRCWithView:self.tableView
                                                                  reciver:self
                                                           managedContext:self.managedObjectContext
                                                               entityName:@"Item"
                                                                  sortKey:@"date"
                                                                ascending:YES
                                                                predicate:nil];
}

其中第一參數輸入須要綁定(bind)的 TableView(若是使用DECollectionViewFetchRC則傳入CollectionView),第二個參數是DETableViewFetchDelegate的委託,主要用來實現Cell內容填充回調:

- (UITableViewCell *)tableView:(UITableView *)tableView cellAtIndexPath:(NSIndexPath *)indexPath withData:(id)data
{
    Item* item = data;
    UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
    cell.textLabel.text = item.title;
    cell.detailTextLabel.text = [item.date description];
    return cell;
}

這裏的CoreData實例名字是Item,有title和date兩個屬性,分別爲NSString和NSDate。

這樣就完成了一步綁定,CoreData的Context有變更時自動更新調用Cell更新回調,更新Cell,增刪改都自動完成。效果以下:

最終效果

DEFetchRCDemo.gif

詳細的代碼請參考zsy78191/DEFetchRC

總結

本文做爲怎樣下降iOS代碼耦合性一文的延伸閱讀,介紹了NSFetchedResultsController類的基本使用方法,這個類做爲CoreData的輔助控制器,在哲學高度強化了CoreData和TableView的綁定,原理上也適用於CoreData和CollectionView或其它第三方控件的綁定,靈活中不失穩重,值得細細研究一番。

相關文章
相關標籤/搜索