Sponsor Link
環境
Xcode11.4, MacOS10.15を使います。
Swift5を使って、最新の記述方法で作っていきます。
プロジェクト作成
macOSのAppを選んで、
Swift/SwiftUI/CoreDataを選んで、
プロジェクト作成は、終わり。
CoreData関連のファイルは、後で、Xcodeからコード生成することにもなるので、別グループを作って管理します。
プロジェクト設定の確認
生成されているAppDelegate.swiftのコードを確認すると、以下のようなコードがあります。
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the SwiftUI view and set the context as the value for the managedObjectContext environment keyPath.
// Add `@Environment(\.managedObjectContext)` in the views that will need the context.
let contentView = ContentView().environment(\.managedObjectContext, persistentContainer.viewContext)
// Create the window and set the content view.
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Main Window")
window.contentView = NSHostingView(rootView: contentView)
window.makeKeyAndOrderFront(nil)
}
ここでCoreDataの操作に必要となるNSManagedObjectContextをContentViewのenvironmentに設定していますので、View側からは、@Environmentを使用して参照することができます。
サンプル用の要素作成
作成したアトリビュートに、特に意味はないですが、以下のような要素を作ります。
SwiftUI向け設定
ManagedObjectContextへのアクセスやFetchを用意しておきます。
@Environment(\.managedObjectContext) var moc
@FetchRequest(entity: AppEntity.entity(), sortDescriptors: [] ) var entities: FetchedResults
AppDelegate40行目付近に、以下のコードがあると思いますが、lazyキーワードを削除しないとアプリがクラッシュします。
lazy var persistentContainer: NSPersistentContainer = {
おそらく、評価するタミングの問題ですが、persistentContainerが評価される前に、@FetchRequestが評価されてしまい、クラッシュするようです。@FetchRequestが評価される前に、persistentContainerが評価されれば問題ないのですが、そのようなタイミングで評価するように変更するのは手間なので、lazyキーワードを削除するのが簡単です。
CoreDataアトリビュートWrapper作成
CoreDataエンティティにアクセスしやすいように、Wrapperを作っておきます。String等のAttributeは、Attributeの設定で非Optionalにしても、変数としては、String?等のOptional型になってしまうため、常にUnwrapする必要が発生してしまい、記述が煩雑になってしまいます。特にSwiftUIとは相性が良くないと思いますので、このようなwrapperを作っておくと便利です。
extension AppEntity {
var strAttr:String {
get {
return stringAttr ?? ""
}
set(newValue) {
stringAttr = newValue
}
}
var id: String {
get {
let uuid = uuidAttr ?? UUID()
uuidAttr = uuid
return uuid.uuidString
}
}
}
簡単なUIの作成
CoreData操作のためのUIを作っていきます。
まずは、CoreData内に存在する要素を表示するリスト
VStack {
Text("num of AppEntity \(entities.count)")
List(entities, id:\.self) { entity in
Text("UUID: \(entity.id) String: \(entity.strAttr) Int64: \(entity.int64Attr)")
}
}
作成/削除するためのボタン(とそこから呼ばれる関数)
VStack {
Button(action: {self.createNewEntity()},
label: {Text("Create a new Entity")}
)
Button(action: {self.deleteEntities()},
label: {Text("Delete selected Entities")}
)
}
func createNewEntity() {
// Entity作成
}
func deleteEntities() {
// Entity 削除
}
ここまでのコードをまとめると以下のようになります。
struct ContentView: View {
@Environment(\.managedObjectContext) var moc
@FetchRequest(entity: AppEntity.entity(), sortDescriptors: [] ) var entities: FetchedResults
@State private var selections = Set()
var body: some View {
HStack {
VStack {
Text("num of AppEntity \(entities.count)")
List(entities, id: \.self, selection: $selections) { entity in
Text("id: \(entity.id) int64: \(entity.int64Attr) string: \(entity.strAttr) ")
}
}
VStack {
Button(action: {self.createNewEntity()},
label: {Text("Create a new Entity")}
)
Button(action: {self.deleteEntities()},
label: {Text("Delete selected Entities")}
)
}
}
}
func createNewEntity() {
// Entity作成
}
func deleteEntities() {
// Entity削除
}
}
この段階で機能は動きませんが、実行するとウィンドウが表示されます。
CoreDataでのEntity生成
以前は、insertNewObject等を使っていましたが、いまでは非常に簡単にEntityを作成できます。
let newEntity = AppEntity(context: moc)
CoreDataは、複数のContextを並行して操作することができるので、どのContextに追加するかを指定する必要がありますが、それだけでOKです。
作成した後は、Attributeを設定しておけばokです。
先の // Entity作成 の箇所のコードは以下のようになります。
func createNewEntity() {
let newEntity = AppEntity(context: moc)
newEntity.uuidAttr = UUID()
newEntity.stringAttr = Date().description
newEntity.int64Attr = Int64.random(in: 0...256)
}
UUIDは、そのままUUIDを設定していますが、Stringは、あまり重複しないであろう文字列ということで現在日時を文字列化したものを設定しています。Int64には、乱数をセットしています。
CoreDataでのEntity削除
Entityを削除するためのコードは、以下です。
moc.delete(entity)
Batchで削除(メモリ上に展開せずに削除)する時は、NSBatchDeleteRequestが使えますが、今回は普通に削除します。
選択されたEntityがselectionsに保持されていますので、それを削除して、終わりです。
func deleteEntities() {
for entity in selections {
moc.delete(entity)
}
selections = []
}
CoreDataでの要素の作成削除
ここまでのコードをまとめると、以下になります。
struct ContentView: View {
@Environment(\.managedObjectContext) var moc
@FetchRequest(entity: AppEntity.entity(), sortDescriptors: [] ) var entities: FetchedResults
@State private var selections = Set()
var body: some View {
HStack {
VStack {
Text("num of AppEntity \(entities.count)")
List(entities, id: \.self, selection: $selections) { entity in
Text("id: \(entity.id) int64: \(entity.int64Attr) string: \(entity.strAttr) ")
}
}
VStack {
Button(action: {self.createNewEntity()},
label: {Text("Create a new Entity")}
)
Button(action: {self.deleteEntities()},
label: {Text("Delete selected Entities")}
)
}
}
}
func createNewEntity() {
let newEntity = AppEntity(context: moc)
newEntity.uuidAttr = UUID()
newEntity.stringAttr = Date().description
newEntity.int64Attr = Int64.random(in: 0...256)
}
func deleteEntities() {
for entity in selections {
moc.delete(entity)
}
selections = []
}
}
実行すると以下のようになります。
“Create a new Entity”ボタンを押すと、新しいEntityが生成されます。
“Delete selected Entities”を押すと、選択されている要素が削除されます。
繰り返しですが、AppDelegate 40行目付近のlazyを削除しないとクラッシュします。
CoreData とアーキテクチャ
CoreData をお試して使う分には、Xcode が生成してくれるコードを使って試してみるのが良いと思います。
少し凝った機能を実装しようとすると、適切なアーキテクチャを選択しておくことが重要となります。
MVVM での実装例を説明していますので、以下の記事も参考にしてください。
SwiftUI おすすめ本
SwiftUI を理解するには、以下の本がおすすめです。
# SwiftUI2.0 になって、少し古くなってしまいましたが、定番本です。
CoreData おすすめ本
系統だって理解するには、以下の本がおすすめです。
Sponsor Link