---
# System prepended metadata

title: RxCoreData
tags: [CoreData, Swift]

---

---
title: 'RxCoreData'
tags: CoreData
disqus: hackmd
---

RxCoreData
===

## Table of Contents

[TOC]

## SPM

```shell=
git clone http://192.168.12.29/Cheng-Hong/rxcoredatapackage
```

### Init

```swift=
// TODO: - for RxCoreDataPackage
private let coreDataManager = CoreDataManager("Model")

var managedObjectContext: NSManagedObjectContext {
    coreDataManager.managedObjectContext
}
```

上面程式碼中的"Model"是你的Data Model名稱，例如：

![](https://imgur.com/g9boHWj.png)

## Create Data Model

![](https://imgur.com/V07AEQi.png)

## CoreDataManager

- .documentDirectory -> 把sqlite放在document folder底下
- forResource: "Model" -> Model是你剛剛創建的Data Model名稱
- "RxCoreData.sqlite" -> sqlite的名稱(自訂)

<details>
    <summary>Source Code</summary>

```swift=
import Foundation
import CoreData

class CoreDataManager {
    
    static let shared = CoreDataManager()
    
    // MARK: - Core Data stack
    
    lazy var applicationDocumentsDirectory: URL = {
        let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        return urls.last!
    }()
    
    lazy var managedObjectModel: NSManagedObjectModel = {
        let modelURL = Bundle.main.url(forResource: "Model", withExtension: "momd")!
        return NSManagedObjectModel(contentsOf: modelURL)!
    }()
    
    lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
        let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
        let url = self.applicationDocumentsDirectory.appendingPathComponent("RxCoreData.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 {
            var dict = [String: Any]()
            dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
            dict[NSLocalizedFailureReasonErrorKey] = failureReason
            
            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
    }()
}
```
    
</details>

## Persistable

- 將需要實例化的variable、function創建成Protocol

<details>
    <summary>Source Code</summary>

```swift=
import Foundation
import CoreData

public protocol Persistable {
    associatedtype T: NSManagedObject
    
    static var entityName: String { get }
    
    /// The attribute name to be used to uniquely identify each instance.
    static var primaryAttributeName: String { get }
    
    var identity: String { get }

    init(entity: T)

    func update(_ entity: T)
    
    /* predicate to uniquely identify the record, such as: NSPredicate(format: "code == '\(code)'") */
    func predicate() -> NSPredicate
    
}

public extension Persistable {
    
    func predicate() -> NSPredicate {
        return NSPredicate(format: "%K = %@", Self.primaryAttributeName, self.identity)
    }

}
```

</details>

## NSManagedObjectContext+Rx

- 創建CoreDate Rx的方法

<details>
    <summary>Source Code</summary>

```swift=
import Foundation
import CoreData
import RxSwift

public extension Reactive where Base: NSManagedObjectContext {
    
    /**
     Executes a fetch request and returns the fetched objects as an `Observable` array of `NSManagedObjects`.
     - parameter fetchRequest: an instance of `NSFetchRequest` to describe the search criteria used to retrieve data from a persistent store
     - parameter sectionNameKeyPath: the key path on the fetched objects used to determine the section they belong to; defaults to `nil`
     - parameter cacheName: the name of the file used to cache section information; defaults to `nil`
     - returns: An `Observable` array of `NSManagedObjects` objects that can be bound to a table view.
     */
    func entities<T: NSManagedObject>(fetchRequest: NSFetchRequest<T>,
                                      sectionNameKeyPath: String? = nil,
                                      cacheName: String? = nil) -> Observable<[T]> {
        
        return Observable.create { observer in
            let observerAdapter = FetchedResultsControllerEntityObserver(observer: observer,
                                                                         fetchRequest: fetchRequest,
                                                                         managedObjectContext: self.base,
                                                                         sectionNameKeyPath: sectionNameKeyPath,
                                                                         cacheName: cacheName)
            
            return Disposables.create {
                observerAdapter.dispose()
            }
        }
    }
    
    /**
     Executes a fetch request and returns the fetched section objects as an `Observable` array of `NSFetchedResultsSectionInfo`.
     - parameter fetchRequest: an instance of `NSFetchRequest` to describe the search criteria used to retrieve data from a persistent store
     - parameter sectionNameKeyPath: the key path on the fetched objects used to determine the section they belong to; defaults to `nil`
     - parameter cacheName: the name of the file used to cache section information; defaults to `nil`
     - returns: An `Observable` array of `NSFetchedResultsSectionInfo` objects that can be bound to a table view.
     */
    func sections<T: NSManagedObject>(fetchRequest: NSFetchRequest<T>,
                                      sectionNameKeyPath: String? = nil,
                                      cacheName: String? = nil) -> Observable<[NSFetchedResultsSectionInfo]> {

        return Observable.create { observer in
            let frc = NSFetchedResultsController(fetchRequest: fetchRequest,
                                                 managedObjectContext: self.base,
                                                 sectionNameKeyPath: sectionNameKeyPath,
                                                 cacheName: cacheName)
            
            let observerAdapter = FetchedResultsControllerSectionObserver(observer: observer, frc: frc)
            return Disposables.create {
                observerAdapter.dispose()
            }
        }
    }
    
    /**
     Performs transactional update, initiated on a separate managed object context, and propagating thrown errors.
     - parameter updateAction: a throwing update action
     */
    func performUpdate(updateAction: (NSManagedObjectContext) throws -> Void) throws {
        
        let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
        privateContext.parent = self.base
        
        try updateAction(privateContext)
        guard privateContext.hasChanges else { return }
        try privateContext.save()
        try self.base.save()
    }
}

public extension Reactive where Base: NSManagedObjectContext {
    
    /**
     Creates, inserts, and returns a new `NSManagedObject` instance for the given `Persistable` concrete type (defaults to `Persistable`).
     */
    private func create<E: Persistable>(_ type: E.Type = E.self) -> E.T {
        return NSEntityDescription.insertNewObject(forEntityName: E.entityName, into: self.base) as! E.T
    }
    
    private func get<P: Persistable>(_ persistable: P) throws -> P.T? {
        let fetchRequest: NSFetchRequest<P.T> = NSFetchRequest(entityName: P.entityName)
        fetchRequest.predicate = persistable.predicate()
        let result = (try self.base.execute(fetchRequest)) as! NSAsynchronousFetchResult<P.T>
        return result.finalResult?.first
    }
    
    /**
     Attempts to retrieve  remove a `Persistable` object from a persistent store, and then attempts to commit that change or throws an error if unsuccessful.
     - seealso: `Persistable`
     - parameter persistable: a `Persistable` object
     */
    func delete<P: Persistable>(_ persistable: P) throws {
        
        if let entity = try get(persistable) {
            self.base.delete(entity)
            
            do {
                try entity.managedObjectContext?.save()
            } catch let e {
                print(e)
            }
        }
    }
    
    /**
     Creates and executes a fetch request and returns the fetched objects as an `Observable` array of `Persistable`.
     - parameter type: the `Persistable` concrete type; defaults to `Persistable`
     - parameter format: the format string for the predicate; defaults to `""`
     - parameter arguments: the arguments to substitute into `format`, in the order provided; defaults to `nil`
     - parameter sortDescriptors: the sort descriptors for the fetch request; defaults to `nil`
     - returns: An `Observable` array of `Persistable` objects that can be bound to a table view.
     */
    func entities<P: Persistable>(_ type: P.Type = P.self,
                                  predicate: NSPredicate? = nil,
                                  sortDescriptors: [NSSortDescriptor]? = nil) -> Observable<[P]> {
        
        let fetchRequest: NSFetchRequest<P.T> = NSFetchRequest(entityName: P.entityName)
        fetchRequest.predicate = predicate
        fetchRequest.sortDescriptors = sortDescriptors ?? [NSSortDescriptor(key: P.primaryAttributeName, ascending: true)]
        
        return entities(fetchRequest: fetchRequest).map {$0.map(P.init)}
    }
    
    /**
     Attempts to fetch and update (or create if not found) a `Persistable` instance. Will throw error if fetch fails.
     - parameter persistable: a `Persistable` instance
     */
    func update<P: Persistable>(_ persistable: P) throws {
        persistable.update(try get(persistable) ?? self.create(P.self))
    }
    
}
```
    
</details>

## NSManagedObjectContext

- 雖然是RxCoreData，不過有時候只是需要取資料，卻都要通過Observer在存起來有點麻煩，所以另外創建了一個非Rx的讀取

<details>
    <summary>Source Code</summary>
    
```swift=
import Foundation
import CoreData

extension NSManagedObjectContext {
    /**
     Executes a fetch request and returns the fetched objects as an `Observable` array of `NSManagedObjects`.
     - parameter fetchRequest: an instance of `NSFetchRequest` to describe the search criteria used to retrieve data from a persistent store
     - parameter sectionNameKeyPath: the key path on the fetched objects used to determine the section they belong to; defaults to `nil`
     - parameter cacheName: the name of the file used to cache section information; defaults to `nil`
     - returns: An `Observable` array of `NSManagedObjects` objects that can be bound to a table view.
     */
    func entities<T: NSManagedObject>(fetchRequest: NSFetchRequest<T>,
                                      sectionNameKeyPath: String? = nil,
                                      cacheName: String? = nil) -> [T] {
        
        let frc = NSFetchedResultsController(fetchRequest: fetchRequest,
                                             managedObjectContext: self,
                                             sectionNameKeyPath: sectionNameKeyPath,
                                             cacheName: cacheName)
        
        do {
            try frc.performFetch()
        } catch let e {
            print(e)
        }
        
        let entities = frc.fetchedObjects ?? []
        return entities
    }
    
    func fetchData<T>(_ fetchRequest: NSFetchRequest<T>) -> [T] where T : NSFetchRequestResult {
        do {
            return try fetch(fetchRequest)
        } catch {
            print("Fetching Failed")
        }
        return []
    }
}


extension NSManagedObjectContext {
    /**
     Creates and executes a fetch request and returns the fetched objects as an `Observable` array of `Persistable`.
     - parameter type: the `Persistable` concrete type; defaults to `Persistable`
     - parameter format: the format string for the predicate; defaults to `""`
     - parameter arguments: the arguments to substitute into `format`, in the order provided; defaults to `nil`
     - parameter sortDescriptors: the sort descriptors for the fetch request; defaults to `nil`
     - returns: An `Observable` array of `Persistable` objects that can be bound to a table view.
     */
    func entities<P: Persistable>(_ type: P.Type = P.self,
                                  predicate: NSPredicate? = nil,
                                  sortDescriptors: [NSSortDescriptor]? = nil) -> [P] {
        
        let fetchRequest: NSFetchRequest<P.T> = NSFetchRequest(entityName: P.entityName)
        fetchRequest.predicate = predicate
        fetchRequest.sortDescriptors = sortDescriptors ?? [NSSortDescriptor(key: P.primaryAttributeName, ascending: true)]
        
        
//        return fetchData(fetchRequest).map { P.init(entity: $0) }
        return entities(fetchRequest: fetchRequest).map { P.init(entity: $0) }
    }
}
```
    
</details>

## FetchedResultsControllerControllerEntityObserver

- 將fetch request轉換成entities: [NSManagedObject]
- 利用NSFetchedResultsControllerDelegate的controllerDidChangeContent去監聽，當有變化時發送onNext出去

<details>
    <summary>Source Code</summary>
    
```swift=
import Foundation
import CoreData
import RxSwift

public final class FetchedResultsControllerEntityObserver<T: NSManagedObject>: NSObject, NSFetchedResultsControllerDelegate {
    
    typealias Observer = AnyObserver<[T]>
    
    private let observer: Observer
    private let frc: NSFetchedResultsController<T>
    
    init(observer: Observer, fetchRequest: NSFetchRequest<T>, managedObjectContext context: NSManagedObjectContext, sectionNameKeyPath: String?, cacheName: String?) {
        self.observer = observer
        

        self.frc = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: sectionNameKeyPath, cacheName: cacheName)
        super.init()
        
        context.perform {
            self.frc.delegate = self
            
            do {
                try self.frc.performFetch()
            } catch let e {
                observer.on(.error(e))
            }
            
            self.sendNextElement()
        }
    }
    
    private func sendNextElement() {
        self.frc.managedObjectContext.perform {
            let entities = self.frc.fetchedObjects ?? []
            self.observer.on(.next(entities))
        }
    }
    
    public func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        sendNextElement()
    }
    /// Delegate implementation for `Disposable`
    /// required methods - This is kept in here
    /// to make `frc` private.
    public func dispose() {
        frc.delegate = nil
    }
}

extension FetchedResultsControllerEntityObserver : Disposable { }
```
    
</details>

## FetchedResultsControllerSectionObserver(目前未用到)

- 將fetch request轉換成entities: [NSFetchedResultsSectionInfo]
- 利用NSFetchedResultsControllerDelegate的controllerDidChangeContent去監聽，當有變化時發送onNext出去

<details>
    <summary>Source Code</summary>
    
```swift=
import Foundation
import CoreData
import RxSwift

public final class FetchedResultsControllerSectionObserver<T: NSManagedObject> : NSObject, NSFetchedResultsControllerDelegate {
    
    typealias Observer = AnyObserver<[NSFetchedResultsSectionInfo]>
    
    private let observer: Observer
    private let frc: NSFetchedResultsController<T>
    
    init(observer: Observer, frc: NSFetchedResultsController<T>) {
        self.observer = observer
        self.frc = frc
        
        super.init()
        
        self.frc.delegate = self
        
        do {
            try self.frc.performFetch()
        } catch let e {
            observer.on(.error(e))
        }
        
        sendNextElement()
    }
    
    private func sendNextElement() {
        let sections = self.frc.sections ?? []
        observer.on(.next(sections))
    }
    
    public func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        sendNextElement()
    }
    
    public func dispose() {
        frc.delegate = nil
    }
}

extension FetchedResultsControllerSectionObserver : Disposable { }
```
    
</details>

## Model

- 依照Data Model Entities去創建Model，例如下圖：

[![](https://imgur.com/px2RuVV.png)](https://imgur.com/px2RuVV)

<details>
    <summary>Source Code</summary>
    
```swift=
import Foundation
import CoreData
import PhoneNumberKit

struct LoginData {
    var countryCode: String
    
    var phoneNumber: String
    
    var token: String
    
    var refreshToken: String
    
    var uName: String
    
    var nick: String
}

extension LoginData: Persistable {
    typealias T = NSManagedObject
    
    private static let COUNTRYCODE = "countryCode"
    private static let PHONENUMBER = "phoneNumber"
    private static let TOKEN = "token"  // LoginSys Token
    private static let REFRESHTOKEN = "refreshToken" // refresh token
    private static let UNAME = "uName"
    private static let NICK = "nick"
    
    var identity: String {
        return phoneNumber
    }
    
    static var entityName: String {
        return String(describing: self)
    }
    
    static var primaryAttributeName: String {
        return "phoneNumber"
    }
    
    init(entity: T) {
        if let countryCode = entity.value(forKey: LoginData.COUNTRYCODE) as? String {
            self.countryCode = countryCode
        }
        
        if let phoneNumber = entity.value(forKey: LoginData.PHONENUMBER) as? String {
            self.phoneNumber = phoneNumber
        }
        
        if let token = entity.value(forKey: LoginData.TOKEN) as? String {
            self.token = token
        }

        if let refreshToken = entity.value(forKey: LoginData.REFRESHTOKEN) as? String {
            self.refreshToken = refreshToken
        }
        
        if let uName = entity.value(forKey: LoginData.UNAME) as? String {
            self.uName = uName
        }
        
        if let nick = entity.value(forKey: LoginData.NICK) as? String {
            self.nick = nick
        }
    }
    
    func update(_ entity: T) {
        entity.setValue(countryCode, forKey: LoginData.COUNTRYCODE)
        entity.setValue(phoneNumber, forKey: LoginData.PHONENUMBER)
        entity.setValue(token, forKey: LoginData.TOKEN)
        entity.setValue(refreshToken, forKey: LoginData.REFRESHTOKEN)
        entity.setValue(uName, forKey: LoginData.UNAME)
        entity.setValue(nick, forKey: LoginData.NICK)
        
        do {
            try entity.managedObjectContext?.save()
        } catch let e {
            print("CoreData Error: \(e)")
        }
    }
}
```

</details>

## How to use

### Fetch

- Swift

```swift=
private var loginData = CoreDataManager.shared.managedObjectContext.entities(LoginData.self).first ?? LoginData()
```

- RxSwift
- predicate -> 篩選條件(optional)
- sortDescriptors -> 排序方法(optional)
    
```swift=
CoreDataManager.shared.managedObjectContext
    .rx
    .entities(Event.self,
              predicate: NSPredicate(format: "%K == %@", "id", "123"),
              sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)])
    .bind {
        print("LoginData: \($0)")
    }.disposed(by: disposeBag)
```

### Create or update
    
- Swift
    
```swift=
_ = try? CoreDataManager.shared.managedObjectContext.update(loginData)
```
    
- RxSwift

```swift=
_ = try? CoreDataManager.shared.managedObjectContext.rx.update(loginData)
```

### delete
    
- Swift
    
```swift=
do {
    try CoreDataManager.shared.managedObjectContext.delete(loginData)
} catch {
    print(error)
}
```

- RxSwift    

```swift=
do {
    try CoreDataManager.shared.managedObjectContext.rx.delete(loginData)
} catch {
    print(error)
}
```

## CoreData warning

- 這感覺是CoreData的一個bug，因為就算不理他也不會影響我們存取資料

[![](https://imgur.com/LgZk23r.png)](https://imgur.com/LgZk23r)

- 解決方法：

打開專案Folder找到剛剛創建的Data Model，並顯示套件內容

[![](https://imgur.com/NGVO29P.png)](https://imgur.com/NGVO29P)
    
打開contents
    
[![](https://imgur.com/QCX7ycM.png)](https://imgur.com/QCX7ycM)

把紅框的變數整個砍掉之後就不會再出現這個警告了

[![](https://imgur.com/GNpJM83.png)](https://imgur.com/GNpJM83)
    
## CoreData version
    
如果遇到上版後，卻有新的Model要加新增，就必須先創建一個新的version
    
[![](https://imgur.com/6O0nEy4.png)](https://imgur.com/6O0nEy4)
    
選擇新的model

[![](https://imgur.com/f1mNmu2.png)](https://imgur.com/f1mNmu2)
    
並且添加addPersistentStoreOptions
    
``` swift=
static let coreDataManager = CoreDataManager("Model", addPersistentStoreOptions: [
    NSMigratePersistentStoresAutomaticallyOption: true,
    NSInferMappingModelAutomaticallyOption: true
])
```

## 參考資料

[https://github.com/RxSwiftCommunity/RxCoreData](https://github.com/RxSwiftCommunity/RxCoreData)