從Samples中入門IOS開發(二)------ CURD

這段時間在想辦法入門IOS native的開發,想找一個比較快速有效的辦法,看書或者看文章都不太合適,主要是現在確實沒有這麼好的書能讓你看完後就完成了從Java轉到IOS的跨越,並且看完後就容易忘記,不深刻。後來發現一個非常不錯的資源,就是IOS Library中的Sample code,基本掌握Xcode後直接從這些samples的code入手,debug->code->document,這樣的學習方式不但深刻,更重要的是可以把這些code直接轉變爲將來的武器庫,感覺不錯,今天就從CURD開始。

互聯網或企業級應用的基礎組成部分就是CURD,而普通的IOS應用特別是那些以信息管理的爲核心的app也是以CURD爲基礎,因此入門IOS當然得把CURD的各個環節搞清楚,官方samples CoreDataBooks給了我們想知道的細節。對我而言,我最想搞清楚這麼幾塊:

整體流轉模式

頁面流程圖如下所示:


對於這麼一個常規的CURD流程,從中可以學習到的關鍵點有如下幾點:

  • 基於MainStoryBoard來控制頁面流
  • 基於CoreData來管理數據
  • TableView與CoreData的結合
  • 以數據爲驅動的MVC操作模型
  • 基於UndoManager來做撤銷或重做
  • 基於Delegate的代碼組織結構
  • 程序退出時及時保存數據
基於MainStoryBoard來控制頁面流

MainStoryBoard是個好東西,直觀的圖形操作是一方面,最重要的是它基於stack的模型把IOS頁面流轉的模式給固化下來,不但方便了開發者按照固定的模式來組織頁面,也固化了用戶的使用習慣。http://www.raywenderlich.com/5138/beginning-storyboards-in-ios-5-part-1,這篇文章非常詳細地講解了如何開始MainStoryBoard,這裏我就不重複,這個例子中學到的關鍵點有兩點:

1)override方法

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender

爲下個頁面做準備

2)調用UINavigationController的方法popViewControllerAnimated從子頁面回到主頁面

基於CoreData來管理數據

CoreData是Cocoa最核心的數據持久層框架,主要用途就是把提供統一數據操作的API來處理各種不同持久化方式的數據,框架主要提供兩塊,持久層和數據操作層,

持久層:

  • NSManagedObjectContext:是從持久層到數據操作的中介
  • NSManagedObjectModel:構建數據的具體格式
  • NSPersistentStoreCoordinator:提供數據實際的存儲方式
三者的依賴關係是NSManagedObjectContext->NSPersistentStoreCoordinator->NSManagedObjectModel,本例子的是通過一個文件來持久化app中操作的數據,具體初始化這三者的代碼可見sample中CoreDataBooksAppDelegate.

數據操作層:

  • NSFetchedResultsController:數據查找的調用接口
  • NSFetchRequest:數據查找方式的包裝
  • NSEntityDescription:數據實體的包裝
  • NSSortDescriptor:排序規則的包裝
  • NSFetchedResultsControllerDelegate:數據變更的通知
具體如何初始化這些對象可見sample中RootViewController

TableView與CoreData的結合

本例中是基於tableview來展現列表和detail數據,對於tableview的用法一般都是把UITableViewController的datasource和delegate設置爲UITableViewController自身後,然後override UITableViewDatasource和UITableViewDelegate的相關方法,從而填充,渲染和監聽tableview的數據。因此結合CoreData,就是在這些方法裏調用NSFetchedResultsController把數據塞到tableview中,以一下代碼爲例:

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


// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    
    id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
    return [sectionInfo numberOfObjects];
}


// Customize the appearance of table view cells.

- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{    
    // Configure the cell to show the book's title
    Book *book = [self.fetchedResultsController objectAtIndexPath:indexPath];
    cell.textLabel.text = book.title;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{    
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    
    // Configure the cell.
    [self configureCell:cell atIndexPath:indexPath];
    return cell;
}

這幾個方法就設置了tableview的組,行和每行具體數據,並且數據都來源與NSFetchedResultsController,從而與CoreData相結合

以數據爲驅動的MVC操作模型

由上一部分可知,tableview的UI與數據已相分割,而整套程序各個頁面之間的流轉也是基於數據而驅動,這也是IOS強調的MVC模式的體現,每個頁面都有controller對應,每個controller都維護一個data,當data變動時,監聽器會隨之改變UI的展現,比如本例中實現了NSFetchedResultsControllerDelegate,以此監聽data的變動,從而刷新tableview,這樣就不需要在其他頁面改變數據時,而外地修改與此數據相關的UI,具體代碼如下所示:

/*
 NSFetchedResultsController delegate methods to respond to additions, removals and so on.
 */

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
    // The fetch controller is about to start sending change notifications, so prepare the table view for updates.
    [self.tableView beginUpdates];
}


- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
{    
    UITableView *tableView = self.tableView;

    switch(type) {
            
        case NSFetchedResultsChangeInsert:
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
            
        case NSFetchedResultsChangeDelete:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
            
        case NSFetchedResultsChangeUpdate:
            [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
            break;
            
        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}


- (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:UITableViewRowAnimationFade];
            break;
            
        case NSFetchedResultsChangeDelete:
            [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}


- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    // The fetch controller has sent all current change notifications, so tell the table view to process all updates.
    [self.tableView endUpdates];
}

基於UndoManager來做撤銷或重做

IOS提供了一個UndoManager,提供了undo和redo相關功能,UndoManager也是和NSManagedObjectContext相結合,從而實現以數據爲中心的undo和redo,具體說來,UndoManager爲維護一個NSNotificationCenter,併爲此提供Observer的回調,從而達到對undo和redo的監聽的目的,使用方法見如下代碼:

- (void)setUpUndoManager
{
    /*
     If the book's managed object context doesn't already have an undo manager, then create one and set it for the context and self.
     The view controller needs to keep a reference to the undo manager it creates so that it can determine whether to remove the undo manager when editing finishes.
     */
    if (self.book.managedObjectContext.undoManager == nil) {
        
        NSUndoManager *anUndoManager = [[NSUndoManager alloc] init];
        [anUndoManager setLevelsOfUndo:3];
        self.undoManager = anUndoManager;
        
        self.book.managedObjectContext.undoManager = self.undoManager;
    }
    
    // Register as an observer of the book's context's undo manager.
    NSUndoManager *bookUndoManager = self.book.managedObjectContext.undoManager;
    
    NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];
    [dnc addObserver:self selector:@selector(undoManagerDidUndo:) name:NSUndoManagerDidUndoChangeNotification object:bookUndoManager];
    [dnc addObserver:self selector:@selector(undoManagerDidRedo:) name:NSUndoManagerDidRedoChangeNotification object:bookUndoManager];
}
- (void)undoManagerDidUndo:(NSNotification *)notification {

    // Redisplay the data.    
    [self updateInterface];
    [self updateRightBarButtonItemState];
}
- (void)undoManagerDidRedo:(NSNotification *)notification {

    // Redisplay the data.    
    [self updateInterface];
    [self updateRightBarButtonItemState];
}


回調很簡單,實際上就是在數據變更時更新一下UI

基於Delegate的代碼組織結構

Delegate是IOS用得最廣的設計模式,這也是很多Java程序員不習慣的一點,本例中AddViewController中定義了AddViewControllerDelegate,從而把save數據的事情給委派出去,並在RootViewController中進行具體實現,所以RootViewController就是AddViewController的delegate,從而可以把數據操作的代碼集中到一塊兒,想自己實現一套delegate模式的代碼的話可以參考本部分的例子

程序退出時及時保存數據

IOS或者android的app都存在進入background的操作,並且內存清理的模型會導致background裏的app非持久化數據丟失的問題,所以當程序退出(進入background)時要及時持久化數據,本例也實現了這個特性:

- (void)applicationWillTerminate:(UIApplication *)application
{
    [self saveContext];
}


- (void)applicationWillResignActive:(UIApplication *)application
{
    [self saveContext];
}


- (void)applicationDidEnterBackground:(UIApplication *)application
{
    [self saveContext];
}


- (void)saveContext
{
    NSError *error;
    if (_managedObjectContext != nil) {
        if ([_managedObjectContext hasChanges] && ![_managedObjectContext save:&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. 
             */
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        } 
    }
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章