開始用Swift開發(fā)iOS 10 - 17 使用Core Data

上一篇 開始用Swift開發(fā)iOS 10 - 16 介紹靜態(tài)Table Views,UIImagePickerController和NSLayoutConstraint 中添加新建restaurant頁面,但最后數(shù)據(jù)并沒有保存下來脓豪,這一篇使用Core Data方式來持久化保存數(shù)據(jù)禁熏。

數(shù)據(jù)持久化一般是指數(shù)據(jù)庫保存准夷。在Web開發(fā)中糙及,常用Oracle或MySQL等關(guān)系數(shù)據(jù)庫來保存數(shù)據(jù),通過SQL語句查詢尖阔。在iOS中對應(yīng)的數(shù)據(jù)庫是SQLite。Core Data不是數(shù)據(jù)庫榨咐,它是讓開發(fā)者通過面向?qū)ο蠓绞脚c數(shù)據(jù)庫進(jìn)行交互的庫介却。

使用Core Data的例子

新建一個使用Core Data的項目,在AppDelegate類中會比平常多了一個變量和一方法块茁,另外還多了一個文件CoreDataDemo.xcdatamodeld齿坷。

    lazy var persistentContainer: NSPersistentContainer = {
        /*
         The persistent container for the application. This implementation
         creates and returns a container, having loaded the store for the
         application to it. This property is optional since there are legitimate
         error conditions that could cause the creation of the store to fail.
        */
        let container = NSPersistentContainer(name: "CoreDataDemo")
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() 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.
                 
                /*
                 Typical reasons for an error here include:
                 * The parent directory does not exist, cannot be created, or disallows writing.
                 * The persistent store is not accessible, due to permissions or data protection when the device is locked.
                 * The device is out of space.
                 * The store could not be migrated to the current model version.
                 Check the error message to determine what the actual problem was.
                 */
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        return container
    }()

    // MARK: - Core Data Saving support

    func saveContext () {
        let context = persistentContainer.viewContext
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() 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.
                let nserror = error as NSError
                fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
            }
        }
    }
  • 變量persistentContainerNSPersistentContainer的實例,let container = NSPersistentContainer(name: "CoreDataDemo")對應(yīng)CoreDataDemo.xcdatamodeld文件数焊,如果是自己添加時名字需要對應(yīng)永淌。
  • 當(dāng)數(shù)據(jù)變化(insert/update/delete)時 ,調(diào)用saveContext方法保存數(shù)據(jù)佩耳。

向項目中添加Data Model

  • 右擊FoodPin文件夾遂蛀,選擇新建Data Model文件,文件名為FoodPin蚕愤。
  • 選中新生成的FoodPin.xcdatamodeld答恶,添加一個Restaurant Entity饺蚊,然后再在此Entity下添加一些屬性。

選中特定屬性后可在右側(cè)檢查器中設(shè)置相關(guān)特性,比如是否強制需要。


創(chuàng)建Managed Objects

Core Data框架中的 Managed ObjectsEntity之間的關(guān)系座舍,有點像代碼中 接口變量UI objects之間的關(guān)系锁摔。xcode可自動生成Managed Objects

  • 選中Restaurant Entity粮揉,在檢查器中修改class的nameRestaurantMOCodegenClass Definition

  • command-R 或 comman-B一下苗缩,表面上沒有什么變化,在project navigator中沒有多出文件声诸。實際上已經(jīng)生成RestaurantMO類酱讶,代碼已經(jīng)可以使用了,如果使用command+點擊 RestaurantMO彼乌,就可以看到RestaurantMO的代碼:

  • 修改相關(guān)受影響的代碼

    • RestaurantTableViewController.swift
      重新定義restaurants:
      var restaurants:[RestaurantMO] = []
      由于CoreData中存儲圖片是二進(jìn)制泻肯,引用時不能用文件名:

      cell.thumbnailImageView.image = UIImage(data: restaurants[indexPath.row].image as! Data)
      
      if let imageToShare = UIImage(data: self.restaurants[indexPath.row].image as! Data) {
      

      由于RestaurantMO的屬性值是可選值,使用時需要解包:

      let defaultText = "Just checking in at " + self.restaurants[indexPath.row].name! 
    
    • RestaurantDetailViewController.swift

       var restaurant:RestaurantMO!
      
      restaurantImageView.image = UIImage(data: restaurant.image as! Data)
      
       geoCoder.geocodeAddressString(restaurant.location!, completionHandler: { placemarks, error in
      
    • MapViewController.swift

       var restaurant:RestaurantMO!
      
      leftIconView.image = UIImage(data: restaurant.image as! Data)
      
    • ReviewViewController.swift

       var restaurant:RestaurantMO!
      
      restaurantImageView.image = UIImage(data: restaurant.image as! Data)       
       ```
      
      

現(xiàn)在能成功運行慰照,發(fā)現(xiàn)是沒有數(shù)據(jù)的灶挟。

保存新數(shù)據(jù)到數(shù)據(jù)庫

  • AddTableViewController.swift中引入Core Data:import CoreData。添加變量var restaurant:RestaurantMO!毒租。
  • AppDelegate中加入上面例子一個變量和一方法稚铣。
// MARK: - Core Data stack
    
    lazy var persistentContainer: NSPersistentContainer = {
        /*
         The persistent container for the application. This implementation
         creates and returns a container, having loaded the store for the
         application to it. This property is optional since there are legitimate
         error conditions that could cause the creation of the store to fail.
         */
        let container = NSPersistentContainer(name: "FoodPin")
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() 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.
                
                /*
                 Typical reasons for an error here include:
                 * The parent directory does not exist, cannot be created, or disallows writing.
                 * The persistent store is not accessible, due to permissions or data protection when the device is locked.
                 * The device is out of space.
                 * The store could not be migrated to the current model version.
                 Check the error message to determine what the actual problem was.
                 */
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        return container
    }()
    
    // MARK: - Core Data Saving support
    
    func saveContext () {
        let context = persistentContainer.viewContext
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() 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.
                let nserror = error as NSError
                fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
            }
        }
    }
  • AddTableViewControllersave方法的dismiss之前插入:
    // 1
    if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) {
        restaurant = RestaurantMO(context: appDelegate.persistentContainer.viewContext)
        restaurant.name = nameTextField.text
        restaurant.type = typeTextField.text
        restaurant.location = locationTextField.text
        restaurant.isVisited = isVisited
        
        if let restaurantImage = photoImageView.image {
            // 2
            if let imageData = UIImagePNGRepresentation(restaurantImage) {
                restaurant.image = NSData(data: imageData)
            }
        }
        
        print("Saving data to context ...")
        appDelegate.saveContext()
    }
  • UIApplication.shared這種形式是iOS SDK中比較常用單例模式,就是通過一個類屬性shared獲取整個app運行過程只需要一個實例的方法墅垮。UIApplication.shared.delegate as? AppDelegate就獲取了AppDelegate對象惕医。
  • 獲取圖片的二進(jìn)制數(shù)據(jù)對象。

運行噩斟,添加新的restaurant后并沒有在Food Pin中顯示曹锨,實際已經(jīng)添加到數(shù)據(jù)庫中,在RestaurantTableViewController里沒有向數(shù)據(jù)庫獲取剃允。

通過CoreData獲取數(shù)據(jù)

  • RestaurantTableViewController.swift中添加import CoreData沛简。實現(xiàn)協(xié)議NSFetchedResultsControllerDelegate,這個協(xié)議中有方法斥废,任何時候當(dāng)獲取來的數(shù)據(jù)有變化時立即通知代理椒楣。
    class RestaurantTableViewController: UITableViewController, NSFetchedResultsControllerDelegate
  • 定義一個變量
    var fetchResultController: NSFetchedResultsController<RestaurantMO>!
  • viewDidLoad中添加
        // 1   
        let fetchRequest: NSFetchRequest<RestaurantMO> = RestaurantMO.fetchRequest()
        // 2
        let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
        fetchRequest.sortDescriptors = [sortDescriptor]
        if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) {
            let context = appDelegate.persistentContainer.viewContext
            fetchResultController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
            fetchResultController.delegate = self
        }
        
        do {
            // 3
            try fetchResultController.performFetch()
            if let fetchedObjects = fetchResultController.fetchedObjects {
                // 4
                restaurants = fetchedObjects
            }
        } catch {
            print(error)
        }
  • 1 從RestaurantMO對象獲得數(shù)據(jù)請求對象NSFetchRequest
  • 2 通過NSSortDescriptor來設(shè)置獲取結(jié)果的排序方式牡肉。
  • 3 performFetch方法執(zhí)行從數(shù)據(jù)庫中獲取數(shù)據(jù)請求捧灰。
  • 4 把請求結(jié)果復(fù)制給變量restaurants
  • 數(shù)據(jù)庫中數(shù)據(jù)變化,將調(diào)用來自NSFetchedResultsControllerDelegate三個方法毛俏,調(diào)用三個方法的時間可以簡單的理解分別為數(shù)據(jù)將要改變炭庙、數(shù)據(jù)正在改變、數(shù)據(jù)改變后:
    controllerWillChangeContent(_:)
    controller(_:didChange:at:for:newIndexPath:)
    controllerDidChangeContent(_:)

    方法的實現(xiàn)煌寇,也分別對table view有不同處理:

    func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        tableView.beginUpdates()
    }
    
    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type:
        NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
        switch type {
        case .insert:
            if let newIndexPath = newIndexPath {
                tableView.insertRows(at: [newIndexPath], with: .fade)
            }
        case .delete:
            if let indexPath = indexPath {
                tableView.deleteRows(at: [indexPath], with: .fade)
            }
        case .update:
            if let indexPath = indexPath {
                tableView.reloadRows(at: [indexPath], with: .fade)
            } default:
                tableView.reloadData()
        }
        if let fetchedObjects = controller.fetchedObjects {
            restaurants = fetchedObjects as! [RestaurantMO]
        }
    }
    
    func controllerDidChangeContent(_ controller:
        NSFetchedResultsController<NSFetchRequestResult>) {
        tableView.endUpdates()
    }

現(xiàn)在運行程序焕蹄,添加新的Restaurant就能同步顯示了。

通過CoreData刪除數(shù)據(jù)

更新RestaurantTableViewControllertableView(_:editActionsForRowAt:_)方法中的 deleteAction阀溶。

let deleteAction = UITableViewRowAction(style: .default, title: "Delete", handler: {
            (action, indexPath) -> Void in
            
            if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) {
                let context = appDelegate.persistentContainer.viewContext
                let restaurantToDelete = self.fetchResultController.object(at: indexPath)
                context.delete(restaurantToDelete)
                
                appDelegate.saveContext()
            }
        })

現(xiàn)在刪除一項后腻脏,重新啟動后,數(shù)據(jù)消失银锻。

更新數(shù)據(jù)

更新RestaurantDetailViewController的中的ActionratingButtonTapped:

    @IBAction func ratingButtonTapped(segue: UIStoryboardSegue) {
        if let rating = segue.identifier {
            restaurant.isVisited = true
            
            switch rating {
            case "great":
                restaurant.rating = "Absolutely love it! Must try."
            case "good":
                restaurant.rating = "Pretty good."
            case "dislike":
                restaurant.rating = "I don't like it."
            default:
                break
            }
        }
        
        if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) {
            appDelegate.saveContext()
        }
        
        tableView.reloadData()
    }

現(xiàn)在評價一項后永品,重新啟動后評價就會保留。

Exercise:添加新字段

之前新建Restaurant頁面沒有Phone字段击纬,現(xiàn)在添加

  • 在SB的New Restaurant添加新Cell鼎姐,在AddRestaurantController中添加相關(guān)接口并關(guān)聯(lián)。
  • 更新AddRestaurantController中的save:Action相關(guān)代碼掉弛。

代碼

Beginning-iOS-Programming-with-Swift

說明

此文是學(xué)習(xí)appcode網(wǎng)站出的一本書 《Beginning iOS 10 Programming with Swift》 的一篇記錄

系列文章目錄

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市芋肠,隨后出現(xiàn)的幾起案子乎芳,更是在濱河造成了極大的恐慌,老刑警劉巖帖池,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奈惑,死亡現(xiàn)場離奇詭異,居然都是意外死亡睡汹,警方通過查閱死者的電腦和手機肴甸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來囚巴,“玉大人原在,你說我怎么就攤上這事⊥妫” “怎么了庶柿?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長秽浇。 經(jīng)常有香客問我浮庐,道長,這世上最難降的妖魔是什么柬焕? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任审残,我火速辦了婚禮梭域,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘搅轿。我一直安慰自己碰辅,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布介时。 她就那樣靜靜地躺著没宾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪沸柔。 梳的紋絲不亂的頭發(fā)上循衰,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天,我揣著相機與錄音褐澎,去河邊找鬼会钝。 笑死,一個胖子當(dāng)著我的面吹牛工三,可吹牛的內(nèi)容都是我干的迁酸。 我是一名探鬼主播,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼俭正,長吁一口氣:“原來是場噩夢啊……” “哼奸鬓!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起掸读,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤串远,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后儿惫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體澡罚,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年肾请,在試婚紗的時候發(fā)現(xiàn)自己被綠了留搔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡铛铁,死狀恐怖隔显,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情避归,我是刑警寧澤荣月,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站梳毙,受9級特大地震影響哺窄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一萌业、第九天 我趴在偏房一處隱蔽的房頂上張望坷襟。 院中可真熱鬧,春花似錦生年、人聲如沸婴程。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽档叔。三九已至,卻和暖如春蒸绩,著一層夾襖步出監(jiān)牢的瞬間衙四,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工患亿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留传蹈,地道東北人。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓步藕,卻偏偏與公主長得像惦界,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子咙冗,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內(nèi)容