這段時間在想辦法入門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是個好東西,直觀的圖形操作是一方面,最重要的是它基於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:提供數據實際的存儲方式
數據操作層:
- NSFetchedResultsController:數據查找的調用接口
- NSFetchRequest:數據查找方式的包裝
- NSEntityDescription:數據實體的包裝
- NSSortDescriptor:排序規則的包裝
- NSFetchedResultsControllerDelegate:數據變更的通知
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();
}
}
}