最小のコアデータ+ Swift:最小要件(パート2)

これはコアデータの3部作の2番目のパートです。最初のパートは、コアデータ+最小のSwift:最小要件(パート1)から入手できます。



最初の部分では、コアデータ、メインコンポーネント(NSManagedObjectModel、NSPersistentStoreCoordinator、NSManagedObjectContext)、データモデルエディターに関する一般的な情報を知り、データモデルを作成しました。



このパートでは、オブジェクトを操作し、NSEntityDescriptionおよびNSManagedObjectに精通し、クラスを自動生成し、コアデータの使いやすさを大幅に向上させるヘルパークラスを作成します。





NSEntityDescriptionおよびNSManagedObject



NSEntityDescriptionから始めましょう。名前から推測できるように、これは本質の説明を含むオブジェクトです。 データモデルエディターのエンティティで空想したすべてのもの(属性、関係、削除ルールなど)は、このオブジェクトに含まれています。 それを使って行う唯一のことは、それを受け取り、パラメーターとしてどこかに渡すことです。



NSManagedObjectは、管理対象エンティティ自体、 つまりエンティティインスタンスです。 DBMS(前の記事で開始)との類推を続けると、NSManagedObjectはデータベーステーブルのレコード(行)であると言えます。



これを扱う方法を理解するために、新しい顧客を作成しましょう。 既製のインターフェースはまだないので(これについては次の記事で扱います)、アプリケーションデリゲートモジュール( AppDelegate.swift



)で直接プログラミングしてみましょう。 心配しないでください、これは理解のために重要なデモンストレーションにすぎません。少し後で、ここからすべてを別の場所に移します。 次の関数の操作を示すために使用します。



 func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { //      // … return true }
      
      





管理対象オブジェクト(この場合は顧客)の作成は、次のように実行されます。



 func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { //   let entityDescription = NSEntityDescription.entityForName("Customer", inManagedObjectContext: self.managedObjectContext) //    let managedObject = NSManagedObject(entity: entityDescription!, insertIntoManagedObjectContext: self.managedObjectContext) return true }
      
      





まず、エンティティの説明( entityDescription )を取得し、必要なエンティティの名前と対応するコンストラクタへのコンテキストへのリンクを含む文字列を渡します。 仕組み:最初の部分から思い出したように、管理対象オブジェクトのコンテキストは永続ストレージのコーディネーターに接続され、コーディネーターは指定された名前でエンティティが検索されるデータオブジェクトモデルに接続されます。 この関数はオプションの値を返すことに注意してください。



次に、取得したエンティティの説明に基づいて、管理対象オブジェクト自体( managedObject )を作成します。 2番目のパラメーターは、このオブジェクトを作成するコンテキストを渡します(一般的な場合、覚えているように、いくつかのコンテキストがある場合があります)。



さて、オブジェクトを作成しましたが、どのようにしてその属性の値を設定しますか? このため、エンコードはKey-Valueタイプであり、その本質は2つの汎用メソッドがあり、1つは指定された名前で指定された値を設定し、2番目は指​​定された名前で値を抽出することです。 見た目よりもずっと難しく聞こえます。



  func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { //   let entityDescription = NSEntityDescription.entityForName("Customer", inManagedObjectContext: self.managedObjectContext) //    let managedObject = NSManagedObject(entity: entityDescription!, insertIntoManagedObjectContext: self.managedObjectContext) //    managedObject.setValue(" «»", forKey: "name") //    let name = managedObject.valueForKey("name") print("name = \(name)") return true }
      
      





コンソール出力:

  name = Optional( «»)
      
      





ご覧のとおり、すべてが非常に簡単です。 どうぞ 次に、このオブジェクトをデータベースに保存する必要があります。 オブジェクトを作成しただけでは不十分ですか? いいえ、 オブジェクトは特定の特定のコンテキストで「存続」し、そこにのみ存在します。 そこで作成、変更、削除することもできますが、これはすべて特定のコンテキスト内で行われます。 コンテキストへのすべての変更を明示的に保存するまで、実際のデータは変更しません。 編集用に開いたディスク上のファイルとの類似性を描くことができます-「保存」ボタンをクリックするまで、変更は記録されません。 実際、データを処理するプロセス全体を最適化することは非常に便利で素晴らしいことです。



コンテキストの変更の保存は基本的に行われます:

  managedObjectContext.save()
      
      





デリゲートモジュールには、「スマート」な保存のための既製の関数さえあります(前の記事で渡しました)、データが実際に変更された場合にのみ記録が行われます。



  func saveContext () { if managedObjectContext.hasChanges { do { try managedObjectContext.save() } catch { let nserror = error as NSError NSLog("Unresolved error \(nserror), \(nserror.userInfo)") abort() } } }
      
      





したがって、オブジェクトを作成および書き込むためのコード全体は次のようになります。



  func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { //   let entityDescription = NSEntityDescription.entityForName("Customer", inManagedObjectContext: self.managedObjectContext) //    let managedObject = NSManagedObject(entity: entityDescription!, insertIntoManagedObjectContext: self.managedObjectContext) //    managedObject.setValue(" «»", forKey: "name") //    let name = managedObject.valueForKey("name") print("name = \(name)") //   self.saveContext() return true }
      
      





オブジェクトを作成し、データベースに記録しました。 どうやってそれを取り戻すのでしょうか? これはそれほど複雑ではありません。 コードを見てみましょう。

  let fetchRequest = NSFetchRequest(entityName: "Customer") do { let results = try self.managedObjectContext.executeFetchRequest(fetchRequest) } catch { print(error) }
      
      





ここでNSFetchRequestリクエストオブジェクトを作成し、データを受け取るエンティティの名前をパラメーターとしてコンストラクターに渡します。 次に、コンテキストメソッドを呼び出して、このリクエストをパラメーターとして渡します。 これはレコードを取得するための最も簡単なオプションです。一般に、NSFetchRequestは非常に柔軟で、特定の条件下でデータを取得するための広範なオプションを提供します。 記事の次のパートで検討するデータのフィルタリングと並べ替えの例を参考にしてください。



重要な注意: managedObjectContext.executeFetchRequest関数は常にオブジェクトの配列を返します。オブジェクトが1つしかない場合でも、配列が返されます。オブジェクトがまったくない場合は空の配列になります。



上記に基づいて、次の機能テキストがあります。

  func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { //   let entityDescription = NSEntityDescription.entityForName("Customer", inManagedObjectContext: self.managedObjectContext) //    let managedObject = NSManagedObject(entity: entityDescription!, insertIntoManagedObjectContext: self.managedObjectContext) //    managedObject.setValue(" «»", forKey: "name") //    let name = managedObject.valueForKey("name") print("name = \(name)") //   self.saveContext() //   let fetchRequest = NSFetchRequest(entityName: "Customer") do { let results = try self.managedObjectContext.executeFetchRequest(fetchRequest) for result in results as! [NSManagedObject] { print("name - \(result.valueForKey("name")!)") } } catch { print(error) } return true }
      
      





コンソール出力:

 name = Optional( «») name -  «» name -  «»
      
      





オブジェクトを受け取るとすぐに、上記のリストではループ内の結果変数であり、それを任意に編集(新しいオブジェクトの属性設定と違いはありません)、または削除できます。 削除は、コンテキスト変数の対応するメソッドを呼び出すことにより実行され、削除されたオブジェクトはパラメーターとして渡されます。

 self.managedObjectContext.deleteObject(result)
      
      





削除後、コンテキストの永続化を強制する必要もあります。忘れないでください。



小さなオプションの追加
テーブルレベルでコアデータをより「タッチ」したい場合は、見た目よりも簡単です。 シミュレータを使用する場合、データベースファイルは次のどこかにあります。

 /Users/<USER>/Library/Developer/CoreSimulator/Devices/<DEVICE_ID>/data/Containers/Data/Application/<APPLICATION_ID>/Documents/<FileName>.sqlite
      
      





アプリケーションにどんな種類のIDがあるかを推測して、このファイルを急いで手動で検索しないでください。 あなたのためにそれをすべて行う素晴らしいユーティリティがあります-SimSim (著者に感謝するためにこの機会を利用します)。



起動後、メニューバーでハングし、次のようになります(バットアイコン):





実際、目的は明らかです。ユーティリティは、シミュレータにインストールされているアプリケーションのストレージのリストを表示し、それらに直接アクセスできるようにします。





SQLiteファイル自体を表示するには、 Datum Freeなどの無料のビューアを使用できます。





コアデータクラスの自動生成



Key-Valueメソッドは、シンプルで汎用性があり、すぐに使用できるという点で優れています。 しかし、印象を損なう2つのポイントがあります:1つは、私たちが望んでいるよりも多くのコードがあり、2つ目は、小道具の名前を文字列として毎回渡すことです、間違いを犯しやすいです(自動補完はありません)。 そして、計算フィールドや独自のコンストラクターなど、管理対象オブジェクトからもう少し機能が必要な場合はどうすればよいでしょうか? Core Dataには解決策があります! NSManagedObjectから継承し、必要なものをすべて追加することで、独自のクラスを簡単に作成できます(さらに、Core Dataが自動的に作成します)。 その結果、マネージオブジェクトを通常のOOPオブジェクトとして操作し、コンストラクターを呼び出して、オートコンプリートを使用して「ポイントを介して」フィールドにアクセスすることで作成できます(つまり、OOPのすべての力はユーザーの手にあります)。



データモデルエディターを開き、エンティティを選択します。 メニューで選択します(コンテキスト依存なので、エンティティを選択する必要があります) Editor \ Create NSManagedObject Subclass ...







データモデル選択ウィンドウが開きます。 はい、一般的に、いくつかの独立したデータモデルが存在する場合がありますが、1つあるため、選択は明らかです。





次のウィンドウで、クラスを生成する必要があるエンティティを選択するように求められます。すべてを一度に選択しましょう。





次の標準ウィンドウはおなじみのはずです。警告を表示できるのは、 「プリミティブデータ型にスカラープロパティを使用する」オプションだけです。 このオプションの意味は何ですか:このオプションが選択されていない場合、プリミティブデータ型(Float、Double、Intなど)の代わりに、内部に値を含む一種の「ラッパー」が使用されます。 これはObjective-Cに当てはまる可能性が高いです。これは、 Optionalなどのオプションがないためです。 ただし、私たちはSwiftを使用しているため、このオプションを選択しない理由はありません(おそらく、経験豊富な同僚がコメントで修正してくれるでしょう)。







その結果、Core Dataはいくつかのファイルを作成します。これらのファイルが何であるかを見てみましょう。





各エンティティは、たとえば次のようなファイルのペアで表されます。



また、何らかの理由でデータモデルを変更する場合は、これらの生成されたクラスを再作成できます。 この場合、最初のファイル( Customer.swift



)はそのまま残り、2番目のファイル( Customer+CoreDataProperties.swift



)は新しいファイルに完全に置き換えられます。



さて、エンティティのクラスを作成しました。クラスのフィールドに「ポイントを介して」アクセスできるようになったので、サンプルをより馴染みのある外観にしましょう。

  func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { //   let entityDescription = NSEntityDescription.entityForName("Customer", inManagedObjectContext: self.managedObjectContext) //    let managedObject = Customer(entity: entityDescription!, insertIntoManagedObjectContext: self.managedObjectContext) //    managedObject.name = " «»" //    let name = managedObject.name print("name = \(name)") //   self.saveContext() //   let fetchRequest = NSFetchRequest(entityName: "Customer") do { let results = try self.managedObjectContext.executeFetchRequest(fetchRequest) for result in results as! [Customer] { print("name - \(result.name!)") } } catch { print(error) } return true }
      
      





はるかに良い。 しかし、オブジェクトの作成は少し重く見えます。 このすべてをコンストラクターで非表示にすることも可能ですが、そのためには、オブジェクトを作成する管理コンテキストへのリンクが必要です。 ところで、ここではCore Data Stackが定義されているので、デリゲートモジュールでコードを作成しています。 たぶんあなたはもっと良いものを思いつくことができますか?



コアデータマネージャー



コアデータを使用する際の最も一般的な方法は、コアデータスタックに基づいたシングルトンパターンを使用することです。 シングルトンがグローバルアクセスポイントを持つクラスのインスタンスが1つだけ存在することを保証していることを誰かが知らない、または忘れた場合は、お知らせします。 つまり、クラスには、誰が、いつ、どこからアクセスしたかに関係なく、常に1つのオブジェクトしかありません。 現在、このアプローチを実装していますが、コアデータスタックのグローバルアクセスと管理のためにシングルトンを使用します。



CoreDataManager.swiftという名前の新しい空のファイルを作成します















はじめに、コアデータインポートディレクティブを追加して、シングルトン自体を作成しましょう。

 import CoreData import Foundation class CoreDataManager { // Singleton static let instance = CoreDataManager() private init() {} }
      
      





それでは、Core Dataに関連するすべての関数と定義をアプリケーションデリゲートモジュールから移動しましょう。

 import CoreData import Foundation class CoreDataManager { // Singleton static let instance = CoreDataManager() private init() {} // MARK: - Core Data stack lazy var applicationDocumentsDirectory: NSURL = { let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask) return urls[urls.count-1] }() lazy var managedObjectModel: NSManagedObjectModel = { let modelURL = NSBundle.mainBundle().URLForResource("core_data_habrahabr_swift", withExtension: "momd")! return NSManagedObjectModel(contentsOfURL: modelURL)! }() lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = { let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel) let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("SingleViewCoreData.sqlite") var failureReason = "There was an error creating or loading the application's saved data." do { try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: nil) } catch { var dict = [String: AnyObject]() dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data" dict[NSLocalizedFailureReasonErrorKey] = failureReason dict[NSUnderlyingErrorKey] = error as NSError let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict) NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)") abort() } return coordinator }() lazy var managedObjectContext: NSManagedObjectContext = { let coordinator = self.persistentStoreCoordinator var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType) managedObjectContext.persistentStoreCoordinator = coordinator return managedObjectContext }() // MARK: - Core Data Saving support func saveContext () { if managedObjectContext.hasChanges { do { try managedObjectContext.save() } catch { let nserror = error as NSError NSLog("Unresolved error \(nserror), \(nserror.userInfo)") abort() } } } }
      
      







これでシングルトンができ、アプリケーションのどこからでもコアデータスタックにアクセスできます。 たとえば、マネージコンテキストの呼び出しは次のようになります。

 CoreDataManager.instance.managedObjectContext
      
      





ここで、管理オブジェクトを作成するために必要なものすべてをコンストラクターに転送しましょう。



 // Customer.swift // core-data-habrahabr-swift import Foundation import CoreData class Customer: NSManagedObject { convenience init() { //   let entity = NSEntityDescription.entityForName("Customer", inManagedObjectContext: CoreDataManager.instance.managedObjectContext) //    self.init(entity: entity!, insertIntoManagedObjectContext: CoreDataManager.instance.managedObjectContext) } }
      
      





アプリケーションデリゲートモジュールに戻って、いくつか変更を加えましょう。 最初に、管理対象オブジェクトの作成が1行に簡略化され(クラスの新しいコンストラクターを呼び出します)、次に管理対象コンテキストへのリンクが作成されます



 self.managedObjectContext
      
      





次のものに置き換える必要があります



 CoreDataManager.instance.managedObjectContext
      
      







これでコードは完全に馴染みのあるものになり、管理対象オブジェクトでの作業は通常のOOPオブジェクトとそれほど変わりません。

 func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { //    let managedObject = Customer() //    managedObject.name = " «»" //    let name = managedObject.name print("name = \(name)") //   CoreDataManager.instance.saveContext() //   let fetchRequest = NSFetchRequest(entityName: "Customer") do { let results = try CoreDataManager.instance.managedObjectContext.executeFetchRequest(fetchRequest) for result in results as! [Customer] { print("name - \(result.name!)") } } catch { print(error) } return true }
      
      





悪くないですか? あとは、残りのエンティティに対して同様のコンストラクタを作成するだけです。 ただし、最初にコードの量を減らすために別の改善を行ってみましょうCoreDataManager



エンティティの説明を返す関数を作成します。



CoreDataManager.swift



モジュールに戻って、 entityForName



関数を追加しましょう。

 import CoreData import Foundation class CoreDataManager { // Singleton static let instance = CoreDataManager() private init() {} // Entity for Name func entityForName(entityName: String) -> NSEntityDescription { return NSEntityDescription.entityForName(entityName, inManagedObjectContext: self.managedObjectContext)! }
      
      





Customer.swift



モジュールに戻り、次のようにコードを変更します。

 import Foundation import CoreData class Customer: NSManagedObject { convenience init() { self.init(entity: CoreDataManager.instance.entityForName("Customer"), insertIntoManagedObjectContext: CoreDataManager.instance.managedObjectContext) } }
      
      





これで、コードの重複が最小限に抑えられます。 他のエンティティに対して同様のコンストラクタを作成しましょう。 私は1つの例を挙げますが、それは簡単であり、問​​題を引き起こすことはありません(エンティティの名前を除いて、すべてが同じです)。

 // Order.swift // core-data-habrahabr-swift import Foundation import CoreData class Order: NSManagedObject { convenience init() { self.init(entity: CoreDataManager.instance.entityForName("Order"), insertIntoManagedObjectContext: CoreDataManager.instance.managedObjectContext) } }
      
      







結論の代わりに



作成したCoreDataManagerは、Core Dataベースのアプリケーションで使用できるという意味で非常に普遍的であることに注意してください。 プロジェクトに接続する唯一のものは、データモデルファイルの名前です。 これ以上。 つまり、このモジュールを1回記述するだけで、さまざまなプロジェクトで常に使用できます。



次の最後のパートでは、 Storyboard



UITableViewController



して多くの作業を行い、 NSFetchedResultsController



慣れて、もう一度NSFetchRequest



ます。



このプロジェクトはgithubにあります



All Articles