カテゴリー
iOS Swift

CoreDataを学ぶ

この記事とこのYoutubeプレイリストはとても参考になります。

NSManagedObjectはCoreDataのDataModelのあらゆるEntityを表すことが可能です。

下記のようにPerson Entityのname AttributeへString型を格納出来ます。

// appDelegateをゲット
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }

// managedContextをゲット。その後はmanagedContextを介して, KVCを使ってCore Dataにsaveできる		
let managedContext = appDelegate.persistentContainer.viewContext
		
// you can write the above in one line
// let managedContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

let entity = NSEntityDescription.entity(forEntityName: "Person", in: managedContext)!
let person = NSManagedObject(entity: entity, insertInto: managedContext)
person.setValue(name, forKey: "name")
		
do {
  try managedContext.save()
} catch {
  print(error)
}

Core DataからのFetchは以下のように出来ます。これをviewDidLoad内などで必要に応じて呼び出します。

guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
  
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Person")

do {
  // returns an array of [NSManagedObject]
  people = try managedContext.fetch(fetchRequest)
} catch {
  print(error)
}

tableViewではこのようにBinding出来ます。

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  let person = people[indexPath.row]
  let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
  cell.textLabel?.text = person.value(forKeyPath: "name") as? String
  return cell
}

ただ、この仕組みをもっと効率よく(Object-Orientedに)書くことが出来ます。

RecordというEntityがあり、そのattributeとしてname: Stringがあるとします。

EntityのCodegenは Manual/Noneを選択し、Editor -> Create NSManagedObject subclass で以下のようにsubclassを作成出来ます。

import Foundation
import CoreData

@objc(Record)
  public class Record: NSManagedObject {
}

extension Record {
  @nonobjc public class func fetchRequest() -> NSFetchRequest<Record> {
  return NSFetchRequest<Record>(entityName: "Record")
}
  @NSManaged public var name: String
}

extension Record : Identifiable {

}
// get context
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

// create new record
let newRecord = Record(context: context)
let newName = "Your Name"
newRecord.name = newName

do {
  try context.save()
} catch {
  print(error)
}

// to fetch
let request: NSFetchRequest<Record> = Record.fetchRequest()
do {
  // this returns an array of NSManagedObject
  let result = try self.context.fetch(request)
} catch {
  print(error)
}

To Manyとするオブジェクトの順番をCore Dataに保存する場合はOrderedにチェックを入れます。

非標準のオブジェクト(Int配列など)をCore Dataに格納したい場合は下記のように一旦Dataとして格納し(Attribute type は Binary Dataを選択)、使用時にはオブジェクトに戻して使う、などの方法があります。

// to save
let newRecord = Record(context: self.context)
let intArray = [1,2,3]
let arrayAsString = intArray.description
let stringAsData = arrayAsString.data(using: String.Encoding.utf8)
newRecord.arrayData = stringAsData

// to fetch
let request: NSFetchRequest<Record> = Record.fetchRequest()
do {
  let result = try self.context.fetch(request)
  let intArray: [Int] = try JSONDecoder().decode([Int].self, from result.first!.arrayData!)
} catch {
  print(error)
}

複合数のpredicateを組み合わせてcoredataを検索したり、objectを削除したりする場合は下記のようにします。

let predicateNote = NSPredicate(format: "note == %@", note.name)
let predicateID = NSPredicate(format: "chordID == %@" , NSNumber(value: id))
let predicateRootOrTop = NSPredicate(format: "rootOrTop == %@", rootOrTop.name)
let predicateFret = NSPredicate(format: "targetNoteFret == %@", NSNumber(value: fret))
let compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [predicateNote, predicateID, predicateRootOrTop, predicateFret])
		
request.predicate = compoundPredicate
		
do {
  let results = try managedContext.fetch(request)
  if results.count == 0 {
    let newBookmark = Bookmarked(context: managedContext)
    newBookmark.note = note.name
    newBookmark.chordID = Int32(id)
    newBookmark.rootOrTop = rootOrTop.name
    newBookmark.targetNoteFret = Int32(fret)
    try managedContext.save()
  } else {
    let bookmark = results[0]
    managedContext.delete(bookmark)
    try managedContext.save()
  }
} catch {
  print(error)
}

検索結果をソートしたい場合はSort Descriptorを使います。

let sort = NSSortDescriptor(key "name", ascending: true)
let sort = NSSortDescriptor(key "name", ascending: true, selector: "customSort:")
let sort = NSSortDescriptor(key: "name", ascending: true, comparator: { (a, b) -> NSComparisonResult in
 return .OrderAscending
})

fetchRequest.sortDescriptors = [sort]

オブジェクトを削除する際、そのオブジェクトにRelationshipがある場合、delete optionを適切に選ぶ必要があります。例えばownerとdeviceというEntityがあり、ownerがdevice(s)を所有しているrelationshipがあるとします。ownerオブジェクトを削除する場合、deviceのownerに対するrelationshipをどうするのか、以下の4つから選択します。

Nullify – deviceのownerはnilとする

Cascade – device(s)オブジェクトも削除する

Deny – ownerとdevice(s)にrelationshipが存在する場合は削除できない

No Action – プログラマが独自のロジックを用いたい場合

DataModelに変更を加える場合はEditor -> Add Model Versionを選択し、直前のversionから新しいmodelを作成します。

正しいModel versionを選択しないと”An NSManagedObject of class must have a valid NSEntityDescription” エラーが発生します。