【Swift 3】Core Dataを使ってみた【part1】

どうも。

ついにCoreDataを使う必要が出てきたので、現在進行形で奮闘中ですが、同時進行で記事も書いていこうと思います。

CoreData自体の記事はたくさん出てくるのですが、Swift3に対応したのはまだまだ情報が少なく苦戦しています。

 

 

CoreDataってなんだ?

CoreDataとは、iPhoneアプリでデータを永続的に保存できる仕組みの一つです。 データを扱う機能が豊富にそろっているようで、便利なフレームワークのようです

参照:http://www.yoheim.net/blog.php?q=20120616

 

以前CoreDataを触る機会が一度あったのですが、上手くいかず、結局CoreDataを使わずにデータを保存する方法をとりました。

その方法がこちら:【Swift】NSData/NSFileWrapperを利用してデータを保存する

記事はSwift2時代ですのでちょっと違うところもあると思いますがご了承ください・・・(そのうち書き換えます)

NSData等を用いる方法ではなくCoreDataを用いるのは、どのような場合かというと、「大量のデータの中から一部のみを検索して利用する場合」などが挙げられると思います。

実際やってみるとCoreDataの方がすっきりしてデータの管理が楽になると思います。

 

CoreDataを導入しよう!

最初にプロジェクトを作成する際に「 use CoreData」にチェックを入れていなかったので、その状態から始めます。

今回は人物のデータを保存・ロードをしていこうと思います

 

File > New > File を選択

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-09-26-16-59-52

 

 

CoreData > DataModelを選択

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-09-26-17-00-17

 

Next > CreateでDataModelを作成します。

名前は適当にcoredataTestにしました。coredataTest.xcdatamodeledというファイルがファイルに追加されます。

 

次にcoredataTest.xcdatamodeledのファイルを開いて、AddEntityよりEntityを追加します

 

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-09-26-17-06-50

 

 

名前を「Person」にしておきます

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-09-26-17-09-32

 

今回は人物データとして、名前/性別/国 を基本データとして保存しようと思います。

 

Attributesのところで名前/性別/国をそれぞれ追加します。

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-09-26-17-20-01

Typeはそれぞれの型を指定します。

 

relationshipとかはとりあえず置いといてこれだけの情報を保存していきます。

これらを元にエンティティモデルのコードを生成します。

Editor > Create NSManagedObject Subclass

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-09-26-17-23-24

 

Personにチェックを入れて作成。

このようなファイルが追加されていればOKです。

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-09-26-17-23-51

 

(2017/09/07 追記)このとき、もしも「’Person’ is ambiguous for type lookup in this context」というエラーが出た場合は、以下を参考にしてください!

【Swift 3】CoreDataを使うときに「’Entity’ is ambiguous for type lookup in this context」というエラーが出たとき

 

AppDelegateにコードを書こう

use CoreDataにチェックを入れていた場合は既に書かれていますが、途中から導入する場合は手動で追加しなければいけません。

基本的に別プロジェクトをuse CoreDataにチェックを入れた状態で作って、AppDelegateのCoreDataの部分を丸々持ってくればいいのですが、Swift3になってiOS10仕様になってしまいiOS10未満の対応ができなくなってしまいます。

TargetがiOS9や8だった場合は、このようなエラーが出てしまいます。

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-09-26-17-31-07

 

要はこのNSPersistentContainerがiOS10からじゃないと使いないよってことですね。

流石にiOS10のみ対応といくわけにもいかないので(実機がないのでテストもできない!)

ならば昔の方から持って来ればいいんじゃ・・・と思い、古いXcodeがたまたまあったので、そこから流用。

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-09-26-17-36-52

 

こんな感じでエラーがいっぱい出てきますが、言われた通りに全て直していけば大丈夫です。

このままでも、一応iOS10まで動きました。

コピーしてきたときは

    lazy var managedObjectModel: NSManagedObjectModel = {
        // The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model.
        let modelURL = Bundle.main.url(forResource: "coredataTest", withExtension: "momd")!
        return NSManagedObjectModel(contentsOf: modelURL)!
    }()

forResourceの部分を、先ほど作成した.xcdatamodeledファイルの名前と対応するようにしてください。

 

念のため(?)、iOS10では先ほどのNSPersistentContainerを用いるようにしました。

長いですが全文コードを貼っておきます・・・。

AppDelegate.swift

import UIKit
import CoreData


@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    
    var window: UIWindow?
    
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        return true
    }
    
    func applicationWillResignActive(_ application: UIApplication) {
    }
    
    func applicationDidEnterBackground(_ application: UIApplication) {
    }
    
    func applicationWillEnterForeground(_ application: UIApplication) {
    }
    
    func applicationDidBecomeActive(_ application: UIApplication) {
    }
    
    func applicationWillTerminate(_ application: UIApplication) {
        self.saveContext()
    }
    
    
    
    
    // MARK: - Core Data stack
    
    lazy var applicationDocumentsDirectory: NSURL = {
        let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        return urls[urls.count-1] as NSURL
    }()
    
    lazy var managedObjectModel: NSManagedObjectModel = {
        // The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model.
        let modelURL = Bundle.main.url(forResource: "coredataTest", withExtension: "momd")!
        return NSManagedObjectModel(contentsOf: modelURL)!
    }()
    
    lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
        // Create the coordinator and store
        let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
        let url = self.applicationDocumentsDirectory.appendingPathComponent("SingleViewCoreData.sqlite")
        var failureReason = "There was an error creating or loading the application's saved data."
        do {
            try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: nil)
        } catch {
            // Report any error we got.
            var dict = [String: AnyObject]()
            dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data" as AnyObject?
            dict[NSLocalizedFailureReasonErrorKey] = failureReason as AnyObject?
            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
    }()
    
    @available(iOS 10.0, *)
    lazy var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: "coredataTest")
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        return container
    }()
    
    func saveContext () {
        if #available(iOS 10.0, *) {
            let context = persistentContainer.viewContext
            if context.hasChanges {
                do {
                    try context.save()
                } catch {
                    let nserror = error as NSError
                    fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
                }
            }
        } else {
            if managedObjectContext.hasChanges {
                do {
                    try managedObjectContext.save()
                } catch {
                    let nserror = error as NSError
                    NSLog("Unresolved error \(nserror), \(nserror.userInfo)")
                    abort()
                }
            }
        }
    }
}

 

長いのでコメントアウトされている部分は端折りました。

 

データを保存しよう

保存したい場所でimport CoreDataを書いておきます。

 

//セーブする
let appDelegate:AppDelegate = UIApplication.shared.delegate as! AppDelegate
let context:NSManagedObjectContext = appDelegate.managedObjectContext
let entity = NSEntityDescription.entity(forEntityName: "Person", in: context)
let person = NSManagedObject(entity:entity!,insertInto:context) as! Person
person.name = "餅もちこ"
person.gender = false       /*trueならman*/
person.country = "Japan"
do{
   try context.save()
}catch{
   print(error)
}

 

先ほどappDelegateに書いたコードを利用して保存していきます。

上記の書き方はSwift2の書き方でSwift3の書き方はこちら

let appDelegate:AppDelegate = UIApplication.shared.delegate as! AppDelegate
let context:NSManagedObjectContext = appDelegate.managedObjectContext
let person = Person(context:context)
person.name = "餅もちこ"
person.gender = false
person.country = "Japan"
do{
   try context.save()
}catch{
   print(error)
}

 

簡潔ですごくわかりやすいのですが、これまたiOS10からじゃないと使えないよって言われてしまいます。

なのでとりあえず前者の書き方でいいのかなぁと思っています・・・。availableで分ける必要ない、よね・・・?

 

※修正しました(2016/10/16)

一応saveContext()はapplicationWillTerminateで呼んでいるので書いていない場合もありますが、これはアプリが終了する直前に呼ばれるやつなので、やはりその場で呼び出しておいた方がいいんじゃないかなぁと思います。

 

appDelegate.saveContext()ではアプリを終了するとデータが消えてしまいました。

アプリ起動状態では保存されているのですが、一度落とすとデータが消えて保存されないです。

context.save()と書くことで保存に成功しました。

 

データを取り出そう!

データのロードは以下のようにしました。

//読み込む
let fetchRequest:NSFetchRequest<Person> = Person.fetchRequest()
var nameList:Array<String> = []
let fetchData = try! context.fetch(fetchRequest)
if(!fetchData.isEmpty){
   print(fetchData)
      for i in 0..<fetchData.count{
           print(fetchData[i].name)
           nameList.append(fetchData[i].name!)
      }
}

とりあえず名前だけを取り出す感じになっています。

 

全て取り出すときは

 //読み込む
let fetchRequest:NSFetchRequest<Person> = Person.fetchRequest()
var personList:Array<Person> = []
let fetchData = try! context.fetch(fetchRequest)
if(!fetchData.isEmpty){
     for i in 0..<fetchData.count{
          let entity = NSEntityDescription.entity(forEntityName: "Person", in: context)
          let person = NSManagedObject(entity:entity!,insertInto:context) as! Person
          person.name = fetchData[i].name
          person.gender = fetchData[i].gender
          person.country = fetchData[i].country
          personList.append(person)
     }
}

 

こんな感じにしました。

let person = Person()とするとFailed to call Designated Initializer on NSManagedObject Classといったエラーが出てしまったので調べてみると、保存するときと同じようにしないといけないっぽいです。

こちらを参考にしました:Failed to call Designated Initializer on NSManagedObject Class — CoreData

 

結局AppDelegate内に書いたiOS10用のやつは役に立ってない気もしますが気にしない(気になる・・・

 

とりあえずまだこの辺までしか触ってないので、更新・削除等はまた次回に書きます…

part2書きました!【Swift 3】CoreDataを使ってみた。検索・更新・削除編【part2】

 

私自身CoreDataを触るのがほぼ初めてみたいなものなので改善点等ありましたらぜひ教えてください(´・ω・`)(´-ω-`)) ペコリ

 

2018/04/14追記
Swift4でも動作チェック済みです。コードの変更点は特にないです。
でもiOS9はそろそろ切ってもいいかもですね。
Swift ,

Comments...

2016/11/13 01:11

swift3からの初学者です。
coreData の取扱が全くわからずに苦慮しておりましたが、本記事はとても参考になり
自分の環境でも再現させることができました。
ありがとうございました。

アバター
from.ふくまめ
    2016/11/14 08:11

    初めまして。
    お役に立てたようで嬉しいです。
    こちらこそコメントすごく励みになります。
    わざわざありがとうございます!

    アバター
    from.Reo(管理人)
コメントは認証制です。詳しくは下記の注意をお読みください。お気軽にコメントお願いします!

Write a Comment

コメント時の注意

「Twitter」「Facebook」「Google+」「WordPress」のいずれかのアカウントをお持ちの方は各アカウントと連携することでコメントできます。 コメントしたことはSNSに流れませんので、アカウントをお持ちの方はこちらの方法でコメントを投稿して下さると嬉しいです。 アカウントをお持ちでない方はメールアドレスで投稿することができます。 初回コメント時は承認後に表示されます。

Related Memo...

UITableView.RowAnimation の .none はアニメーションするよ

UITableView.RowAnimation の .none はアニメーションがnoneなわけじゃなく、デフォルトの設定を使うよという意味らしい。

The inserted or deleted rows use the default animations.

なのでアニメーションしちゃう。今更の気づき。

 

iOS

記事を書くほどでもないけれどメモっておきたいこと

テスト投稿。

例えばiphone7 の画面サイズ

750 × 1334
半分375 × 667

iOS

UINavigationController + UIScrollView の組み合わせで使っている時に謎の余白ができる時

UINavigationController + UIScrollView の組み合わせで使っていて、UIScrollView 上に AutoLayout で上下左右0で View を設置しているのに、30px程度上にずれてしまうとき。

`navigationController.navigationBar.isTranslucent = false` にすると直るかもしれない。

ScrollView上のコンテンツとNavigationBarの重なっているところが透過していたら多分これで直せるはず。

通常のターゲットではちゃんと動いているのに、iOSSnapshotTestCase を用いたテストでだけこの対応が必要なのよくわからないけれど。。。

iOS
more