CoreDataでSQL発行回数を減らしたいなと思い、relationshipKeyPathsForPrefetching というっぽいプロパティの存在に気づくも、SQL発行回数の削減には役立たなかったのでメモだけ残して帰ります。
挙動を観察
Book と Author モデルを例に、relationshipKeyPathsForPrefetching の有無による挙動を観察する。
import UIKit
import CoreData
class ViewController: UIViewController {
let container = (UIApplication.shared.delegate as! AppDelegate).persistentContainer
@IBAction func selectEntities(_ sender: AnyObject) {
container.performBackgroundTask { (context) in
print("select all")
let req = NSFetchRequest<Book>(entityName: "Book");
//XXX
req.relationshipKeyPathsForPrefetching = ["author"]
let books = try! context.fetch(req)
books.forEach({ (book) in
print(book)
print(book.author?.name)
})
}
}
@IBAction func createEntieies() {
container.performBackgroundTask { (context) in
let author = Author(context: context)
author.name = "kentaro"
let book = Book(context: context)
book.title = "test title"
book.author = author
try! context.save()
print("created")
}
}
@IBAction func clearEntities() {
container.performBackgroundTask { (context) in
let books = try! context.fetch(Book.fetchRequest()) as [Book]
books.forEach({ (book) in
context.delete(book)
})
let authors = try! context.fetch(Author.fetchRequest()) as [Author]
authors.forEach({ (author) in
context.delete(author)
})
try! context.save()
print("cleared")
}
}
}
プリフェッチなしの場合は、Bookを取得する段階ではZBOOKテーブルに対するselectが走るのみで、book.author.name にアクセスした瞬間にZAUTHORテーブルにselectが走る。
プリフェッチなし
CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZTITLE, t0.ZAUTHOR FROM ZBOOK t0
CoreData: annotation: sql connection fetch time: 0.0002s
CoreData: annotation: total fetch execution time: 0.0006s for 1 rows.
<coredata_sample.Book: 0x6180000a86a0> (entity: Book; id: 0xd000000000100000 <x-coredata://3D6FA5D4-7E41-414F-AF67-B30C5480B9BC/Book/p4> ; data: <fault>)
CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME, t0.ZBOOK FROM ZAUTHOR t0 WHERE t0.Z_PK = ?
CoreData: annotation: sql connection fetch time: 0.0001s
CoreData: annotation: total fetch execution time: 0.0002s for 1 rows.
CoreData: annotation: fault fulfilled from database for : 0xd000000000100002 <x-coredata://3D6FA5D4-7E41-414F-AF67-B30C5480B9BC/Author/p4>
Optional("kentaro")
プリフェッチありの場合は、Bookを取得する段階で、ZBOOKテーブルとZAUTHORテーブルに対してselectが一気に走り、book.author.name にアクセスしたときにはクエリは発行されない。
プリフェッチあり
CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZTITLE, t0.ZAUTHOR FROM ZBOOK t0
CoreData: annotation: sql connection fetch time: 0.0001s
CoreData: annotation: Bound intarray _Z_intarray0
CoreData: annotation: Bound intarray values.
CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME, t0.ZBOOK FROM ZAUTHOR t0 WHERE t0.ZBOOK IN (SELECT * FROM _Z_intarray0)
CoreData: annotation: sql connection fetch time: 0.0004s
CoreData: annotation: total fetch execution time: 0.0005s for 1 rows.
CoreData: annotation: Prefetching with key 'author'. Got 1 rows.
CoreData: annotation: total fetch execution time: 0.0018s for 1 rows.
<coredata_sample.Book: 0x6080000b7640> (entity: Book; id: 0xd000000000100000 <x-coredata://3D6FA5D4-7E41-414F-AF67-B30C5480B9BC/Book/p4> ; data: <fault>)
Optional("kentaro")
てっきりZBOOKとZAUTHORをjoinしたクエリを一発だけ発行してくれるのかなーと思ったけど、そんなことはなかった。ということで、クエリ回数の削減にはならない。
クエリ回数の削減はIN句で頑張るのがいいかなー。
Realmとかだとどうなんでしょう。
別件
久しぶりに生のCoreData触ったけど
- NSPersistentContainer
が導入された(iOS10から)
- NSFetchRequest
がgenericsに対応してる
あたりの変更が入ってて、MagicalRecord
とかそろそろ卒業してもいいかなーと思った。