CoreData の relationshipKeyPathsForPrefetching 

CoreDataでSQL発行回数を減らしたいなと思い、relationshipKeyPathsForPrefetching というっぽいプロパティの存在に気づくも、SQL発行回数の削減には役立たなかったのでメモだけ残して帰ります。

# 挙動を観察

Book と Author モデルを例に、relationshipKeyPathsForPrefetching の有無による挙動を観察する。

```swift|ViewController.swift
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`とかそろそろ卒業してもいいかなーと思った。