XCTest only: No NSEntityDescriptions in any model claim the NSManagedObject subclass - swift

Let me outline the relevant code:
I have a DataManager class as follows:
enum DataManagerType {
case normal, preview, testing
}
class DataManager: NSObject, ObservableObject {
static let shared = DataManager(type: .normal)
static let preview = DataManager(type: .preview)
static let testing = DataManager(type: .testing)
#Published var todos = [Todo]()
fileprivate var managedObjectContext: NSManagedObjectContext
private let todosFRC: NSFetchedResultsController<TodoMO>
private init(type: DataManagerType) {
switch type {
case .normal:
let persistentStore = PersistentStore()
self.managedObjectContext = persistentStore.context
case .preview:
let persistentStore = PersistentStore(inMemory: true)
self.managedObjectContext = persistentStore.context
for i in 0..<10 {
let newTodo = TodoMO(context: managedObjectContext)
newTodo.title = "Todo \(i)"
newTodo.isComplete = false
newTodo.date = Date()
newTodo.id = UUID()
}
try? self.managedObjectContext.save()
case .testing:
let persistentStore = PersistentStore(inMemory: true)
self.managedObjectContext = persistentStore.context
}
let todoFR: NSFetchRequest<TodoMO> = TodoMO.fetchRequest()
todoFR.sortDescriptors = [NSSortDescriptor(key: "date", ascending: false)]
todosFRC = NSFetchedResultsController(fetchRequest: todoFR,
managedObjectContext: managedObjectContext,
sectionNameKeyPath: nil,
cacheName: nil)
super.init()
// Initial fetch to populate todos array
todosFRC.delegate = self
try? todosFRC.performFetch()
if let newTodos = todosFRC.fetchedObjects {
self.todos = newTodos.map({todo(from: $0)})
}
}
func saveData() {
if managedObjectContext.hasChanges {
do {
try managedObjectContext.save()
} catch let error as NSError {
NSLog("Unresolved error saving context: \(error), \(error.userInfo)")
}
}
}
private func fetchFirst<T: NSManagedObject>(_ objectType: T.Type, predicate: NSPredicate?) -> Result<T?, Error> {
let request = objectType.fetchRequest()
request.predicate = predicate
request.fetchLimit = 1
do {
let result = try managedObjectContext.fetch(request) as? [T]
return .success(result?.first)
} catch {
return .failure(error)
}
}
}
My persistence store is as such:
struct PersistentStore {
let container: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "CoreDataModel")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.viewContext.automaticallyMergesChangesFromParent = true
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
}
var context: NSManagedObjectContext { container.viewContext }
func saveContext () {
if context.hasChanges {
do {
try context.save()
} catch let error as NSError {
NSLog("Unresolved error saving context: \(error), \(error.userInfo)")
}
}
}
}
I get an error when calling the following in an XCTest:
let predicate = NSPredicate(format: "id = %#", todo.id as CVarArg) //todo.id is just some UUID() //irrelevant here
let result = fetchFirst(TodoMO.self, predicate: predicate)
This is the error I get:
2022-07-09 21:36:17.425709-0400 CoreDataExample[73965:7495035] [error]
error: No NSEntityDescriptions in any model claim the NSManagedObject
subclass 'CoreDataExampleTests.TodoMO' so +entity is confused. Have
you loaded your NSManagedObjectModel yet ? CoreData: error:
No
NSEntityDescriptions in any model claim the NSManagedObject subclass
'CoreDataExampleTests.TodoMO' so +entity is confused. Have you loaded
your NSManagedObjectModel yet ?
2022-07-09 21:36:17.425780-0400 CoreDataExample[73965:7495035] [error] error:
+[CoreDataExampleTests.TodoMO entity] Failed to find a unique match for an NSEntityDescription to a managed object subclass
CoreData: error: +[CoreDataExampleTests.TodoMO entity] Failed to find a unique
match for an NSEntityDescription to a managed object subclass
/Users/santiagogarciasantos/Documents/Xcode
Projects/CoreDataExample/CoreDataExample/DataManager/DataManager.swift:87:
error: -[CoreDataExampleTests.CoreDataExampleTests test_Add_Todo] :
executeFetchRequest:error: A fetch request must have an entity.
(NSInvalidArgumentException)
Following other solutions on here, I've checked the target, made sure my Entity is in "Current Product Module" but it still won't work.
Important: This only occurs when I'm in using XCTest (using my DataManager.testing), not in Previews or in simulator
Here's a link to a demo project I've made which recreates the issue.
Thanks for your help!

I made a couple of changes to your code to make the tests work:
you should not add your app classes to the test target. They are imported automatically by selecting a "Host Application" in you test target and made accessible by the directive #testable import CoreDataExample.
when the test execute, an instance of CoreDataExampleApp is created. This in turn instantiates var dataManager = DataManager.shared.
Then the tests execute where you instantiate dataManager = DataManager.testing.
Two instances of PersistentStore are created which have their own version of the core data stack including the managedObjectModel.
Those models are fighting over your NSManagedObject subclasses which results in objectType.fetchRequest() having no entity.
To fix your issue, go through all files in your app target and make sure in FileInspector>TargetMembership only CoreDataExample is checked. Then in CoreDataExampleTests change line 17 to dataManager = DataManager.shared.
Your tests will run now.
If you want to keep your different DataManager flavours, you have to make sure that only one instance of PersistentStore is ever created. One simple way would be to make it static:
class DataManager: NSObject, ObservableObject {
static let persistentStore = PersistentStore()
// [...]
private init(type: DataManagerType) {
switch type {
case .normal:
self.managedObjectContext = DataManager.persistentStore.context
// etc.
Edit: Alternative Solution
The issue comes from using the TestTarget>General>'Host Application' feature in the first place, which Xcode sets now for new projects. This is meant for Application Tests where you need an app instance.
If you instead want to perform Logic Tests you should opt out of this feature. You then don't get the app instance with its side effects. In your case this is probably what you want. The tests will also run faster because the app does not need to be loaded.
But you then need to add all your files manually to your test target.
To use the alternative approach:
deselect "Host Application"
Manually add all relevant files
You have to help PersistentStore finding the right model file by loading it from an explicit URL. Change this in your test class:
override func setUp() {
super.setUp()
let testBundle = Bundle(for: type(of: self))
let modelUrl = testBundle.url(forResource: "CoreDataModel", withExtension: "momd")
dataManager = DataManager(type: .testing, modelUrl: modelUrl)
}
Change your DataManager and PersistentStore accordingly to handle the optional URL:
// DataManager change not shown (just pass through)
struct PersistentStore {
init(inMemory: Bool = false, modelUrl: URL? = nil) {
if let url = modelUrl {
let mom = NSManagedObjectModel(contentsOf: url)! // todo: handle !
container = NSPersistentContainer(name: "CoreDataModel", managedObjectModel: mom) // todo: maybe get name from URL
} else {
container = NSPersistentContainer(name: "CoreDataModel")
}
I recommend this second approach, I consider it much cleaner.
If you also need Application Tests, add an additional target.

Related

Using CoreData managedContext from background thread...how do you do it correctly?

I have the CoreData concurrency debugger on and I am asserting every where. I cannot figure it out.
I created what I thought was a context on a background thread. Please take a look at my CoreData Stack:
import Foundation
import CoreData
class CoreDataManager {
lazy var managedContext: NSManagedObjectContext = {
return self.storeContainer.viewContext
}()
lazy var backgroundContext: NSManagedObjectContext = {
return self.storeContainer.newBackgroundContext()
}()
lazy var privateMOC: NSManagedObjectContext = {
let pMOC = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
pMOC.parent = self.storeContainer.viewContext
return pMOC
}()
private lazy var storeContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "XXXX")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
print("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
func saveContext () {
guard managedContext.hasChanges else { return }
do {
try managedContext.save()
} catch let error as NSError {
print("Unresolved error \(error), \(error.userInfo)")
}
self.reset()
}
func reset() {
managedContext.reset()
}
}
Then I try to perform a task from a background thread from within a repository type class:
func deleteAllData() {
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Pairing")
let pairings = (try? self.coreDataManager.privateMOC.fetch(fetchRequest)) as! [Pairing_ManagedObject]. // I GET THE ASSERT HERE
for pairing in pairings {
self.coreDataManager.privateMOC.delete(pairing)
}
self.coreDataManager.saveContext()
}
How do I do this so I don't get he core data concurrency assert and do it correctly? Please help.
You're creating a new managed object context with private queue concurrency, but that's not enough to avoid concurrency issues. Core Data's policy is that you must use either performBlock or performBlockAndWait for everything you do that uses that context. In your code that means that the delete and save calls must be be done with one of those functions.
There's a second issue though. You're using your saveContext function to save changes. But that doesn't save changes on your private-queue context. It saves on some other context called managedContext. Saving on one context doesn't automatically save other contexts. If you want to make changes on your private queue context and save those changes, you need to save that context at some point.

Unit test core data with multiple entities

I would like to test my core data methode.
There is multiples entities in my coredataModel and for each I have a NSManagedObject class
there is methode inside those classes to add, delete and remove data of the corresponding entity.
public class StoredGame: NSManagedObject {
static private let storage = DataManager.shared.storage
static var all: [Game] {
let request: NSFetchRequest<StoredGame> = StoredGame.fetchRequest()
guard let storedGame = try? storage.viewContext.fetch(request) else { return [] }
var games: [Game] = .init()
storedGame.forEach { (storedGame) in
games.append(convert(storedGame))
}
return games
}
static func add(new game: Game) {
let entity = NSEntityDescription.entity(forEntityName: "StoredGame", in: storage.viewContext)!
let newGame = StoredGame(entity: entity, insertInto: storage.viewContext)
try? storage.saveContext()
}
}
and then I have a class responsible of the core data stack
class CoreDataManager {
private lazy var persistentContainer: NSPersistentContainer! = {
guard let modelURL = Bundle.main.url(forResource: "CoreData", withExtension:"momd") else {
fatalError("Error loading model from bundle")
}
// The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
guard let mom = NSManagedObjectModel(contentsOf: modelURL) else {
fatalError("Error initializing mom from: \(modelURL)")
}
let container = NSPersistentContainer(name: "CoreData", managedObjectModel: mom)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
var viewContext: NSManagedObjectContext {
return persistentContainer.viewContext
}
func saveContext () throws {
let context = viewContext
if context.hasChanges {
do {
try context.save()
} catch(let error) {
print(error)
}
}
}
}
Then when it goes to the tests. I've created a mockContainer and a mockCoreData
lazy var mockContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "CoreData")
container.persistentStoreDescriptions[0].url = URL(fileURLWithPath: "/dev/null")
container.loadPersistentStores(completionHandler: { (_, error) in
XCTAssertNil(error)
})
return container
}()
lazy var mockCoreData = {
return StoredGame(context: mockContainer.viewContext)
}()
So now I dont know how to run tests in that configuration, I've tried a
XCTAssert(StoredGame.all.isEmpty) for exemple ( i have a all var in the StoredEntity class)
but it fails with an error telling
'NSInvalidArgumentException', reason: '-[CoreData.StoredEntity setId:]: unrecognized selector sent to instance
any idea?
This might be occurring with passing an invalid URL for the store description. Unless you need to run tests with a NSSQLiteStoreType, which is the default for NSPersistentContainer, you may want to consider using an NSInMemoryStoreType for unit testing. A small tweak to your CoreDataManager class could allow you to initialize the class both for your app and unit tests. For example:
class CoreDataManager {
private let persisted: Bool
init(persisted: Bool = true) {
self.persisted = persisted
}
lazy var persistentContainer: NSPersistentContainer = {
let description = NSPersistentStoreDescription()
if persisted {
description.type = NSSQLiteStoreType
description.url = // location to store the db.
} else {
description.type = NSInMemoryStoreType
}
let container = NSPersistentContainer(name: "CoreData")
container.persistentStoreDescriptions = [description]
container.loadPersistentStores //...
return container
}()
}
Then you can use this exact class in your unit tests without need to create a mock. Just initialize it without persistence:
let manager = CoreDataManager(persisted: false)

Using arrayController results in "Cannot perform operation without a managed object context"

I am rewriting my previous objC app in Swift 2.2. This is cocoa application where I am using NSArrayController to fill NSTableView contents.
The error is obvious, although similar setup worked in Objective C app.
Here is my AppDelegate:
var coreStack:AP_CoreDataStack!
var mainContext:NSManagedObjectContext!
override func awakeFromNib() {
coreStack = AP_CoreDataStack(){ (result) -> () in
if result {
self.mainContext = self.coreStack.mainContext
}
}
}
Setup of Core Data Stack
// MARK: - AP_CoreDataStack Class
class AP_CoreDataStack {
let mainContext: NSManagedObjectContext
let mastercontext: NSManagedObjectContext
var workerContext: NSManagedObjectContext?
internal typealias CallBack = (result:Bool) -> Void
init ( callback: CallBack) {
let modelURL = NSBundle.mainBundle().URLForResource("appNameSWIFT", withExtension: "momd")
if (modelURL == nil) {
print("Failed to initialize modelURL: \(modelURL)")
}
let mom = NSManagedObjectModel(contentsOfURL: modelURL!)
if mom == nil {
print("Failed to initialize model")
}
let psc = NSPersistentStoreCoordinator(managedObjectModel: mom!)
mastercontext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
mastercontext.persistentStoreCoordinator = psc
mainContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
mainContext.parentContext = mastercontext
// add store to psc in background thread
let qualityOfServiceClass = QOS_CLASS_BACKGROUND
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
//BACKGROUND THREAD
// adding store to persistent store coordinator
let options = [NSInferMappingModelAutomaticallyOption:true,
NSMigratePersistentStoresAutomaticallyOption:true]
do {
// store = try psc.addP
try psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: applicationDocumentDirectory(), options: options)
} catch let error as NSError {
print("Error: Failed to load store \(error.localizedDescription), \(error.userInfo)")
}
// MAIN THREAD
dispatch_async(dispatch_get_main_queue(), { () -> Void in
// On Main thread pass message that stack setup is complete
callback(result: true)
})
})
}
Above is the Swift version of my Obj C code which worked fine. I have an NSArrayController in xib file which is bound to Entity and NSManagedObjectContext in IB:
// Bind To Delegate
self.mainContext
It seems Array controller is accessing mainContext before it is initialised, but this is the same setup which worked in objC, so why it is causing error in Swift.
EDIT: I am using regular xib file.
EDIT 2:
Evidently mainContext is not nil as calling it here works correctly
func applicationDidFinishLaunching(aNotification: NSNotification) {
// Insert code here to initialize your application
let request = NSFetchRequest(entityName: "AP_EntityA")
let list:Array<AnyObject>
do {
list = try coreStack.mainContext.executeFetchRequest(request)
for item in list {
let product = item as! AP_EntityA
print("item name is: \(product.uniqueName)")
}
} catch let error as NSError {
// failure
print("Fetch failed: \(error.localizedDescription)")
}
}
Add the dynamic keyword to make Swift properties KVO compliant.
dynamic var mainContext:NSManagedObjectContext!

CoreData in background thread in Swift 2.0. Clean way?

I have been struggling with this problem for exactly 8 days now. So I believe it's time to ask for help for the Gurus over here.
Ok, so I am trying to implement an app following Uncle Bob's Clean Architecture, so I have the ViewControllers, Models, Interactors, Repositories and Presenters all setup.
My AppDelegate does not have any traces of CoreData in it, nothing. All that is done in the MyAppCoreData class.
The repository is injected in the AppDelegate and the Interactor access the injected object to access not only CoreData stuff, but also Parse and another private API. The Parse and private API are working great.
The CoreData repository also "works". It does not throw any exception. But no data is inserted in CoreData and when I fetch, it's empty.
I believe I am having some problem with the Persistent Store Coordinator + Main Context + Private Context... but have not yet managed to find out what it is.
So let's go to the code:
AppDelegate
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let container = Container() { c in
c.register(UserDataStore.self) { _ in ParseRepository() }
c.register(BabyNamesDataStore.self) { _ in BabyNameRepository.sharedInstance }
c.register(GenericRepository.self) { r in
GenericRepository(_userDataStore: r.resolve(UserDataStore.self)!, _babyNameDataStore: r.resolve(BabyNamesDataStore.self)!)
}
}
...
Then, my interactors have the following init() function:
class MainInteractor: MainInteractorInput
{
var output: MainInteractorOutput!
var worker: GenericRepository?
init() {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
worker = appDelegate.container.resolve(GenericRepository.self)
}
Then, finally, my CoreDataStore is a singleton (before it wasn't and I could see that it was being instantiated twice for some reason by Swinject):
class BabyNamesCoreDataStore : BabyNamesDataStore{
var mainManagedObjectContext: NSManagedObjectContext
var privateManagedObjectContext: NSManagedObjectContext
static let sharedInstance = BabyNamesCoreDataStore()
init()
{
QL1("BabyNamesCoreDataStore init")
// This resource is the same name as your xcdatamodeld contained in your project.
guard let modelURL = NSBundle.mainBundle().URLForResource("BabyNameMe", withExtension:"momd") else {
fatalError("Error loading model from bundle")
}
// The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
guard let mom = NSManagedObjectModel(contentsOfURL: modelURL) else {
fatalError("Error initializing mom from: \(modelURL)")
}
let psc = NSPersistentStoreCoordinator(managedObjectModel: mom)
mainManagedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
mainManagedObjectContext.persistentStoreCoordinator = psc
let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
let docURL = urls[urls.endIndex-1]
/* The directory the application uses to store the Core Data store file.
This code uses a file named "DataModel.sqlite" in the application's documents directory.
*/
let storeURL = docURL.URLByAppendingPathComponent("BabyNameMe.sqlite")
do {
try psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: nil)
} catch let error as NSError {
QL4("Could not save \(error), \(error.userInfo)")
fatalError("Error migrating store: \(error)")
}
//let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
privateManagedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
privateManagedObjectContext.parentContext = mainManagedObjectContext
}
deinit
{
do {
try self.mainManagedObjectContext.save()
} catch {
fatalError("Error deinitializing main managed object context")
}
}
func fetchBabyNames(completionHandler: (babyNames: [BabyNames], error: StoreError?) -> Void) {
privateManagedObjectContext.performBlock {
do {
let fetchRequest = NSFetchRequest(entityName: "ManagedBabyNames")
let predicate = NSPredicate(format: "liked == nil")
fetchRequest.predicate = predicate
let results = try self.privateManagedObjectContext.executeFetchRequest(fetchRequest) as! [ManagedBabyNames]
let result = results.map { $0.toBabyName() }
completionHandler(babyNames: result, error: nil)
} catch let error as NSError {
QL4("Could not save \(error), \(error.userInfo)")
completionHandler(babyNames: [], error: StoreError.CannotFetch("Cannot fetch baby names"))
}
}
}
func insertBabyNames(babyNames: BabyNames, completionHandler: (error: StoreError?) -> Void) {
privateManagedObjectContext.performBlock {
do {
let managedBabyNames = NSEntityDescription.insertNewObjectForEntityForName("ManagedBabyNames", inManagedObjectContext: self.privateManagedObjectContext) as! ManagedBabyNames
managedBabyNames.fromBabyName(babyNames)
try self.privateManagedObjectContext.save()
completionHandler(error: nil)
} catch let error as NSError {
QL4("Could not save \(error), \(error.userInfo)")
completionHandler (error: StoreError.CannotCreate("Cannot create baby names with id \(babyNames.id)"))
}
}
}
...
And this is pretty much it. I get no exceptions but it is simply not working.
Could anyone please help this desperate man ? :)
Thanks!
UPDATE
I am following Raymund's Clean Store idea to implement my own, found here: https://github.com/Clean-Swift/CleanStore
I found out that the function that receives the response from the Presenter, inside the View Controller is NOT in the main thread. So I had to add this:
func displaySomething(viewModel: LoginViewModel) {
if viewModel.loginStatus {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.prepareSlideOutVC()
//When user is not logged in, the SlideOut Menu is not loaded. So when he logs in, we must load it.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
dispatch_async(dispatch_get_main_queue()) {
self.router.navigateToMainScene()
}
}
}
}
adding the dispatch_async sorted out the problem in a few cases, but not all of them... still trying to figure out.
So, basically, after I login and receive the response from the Presenter, I activate the segue to go to MainVC but that was in a different thread.
Still trying to work this out.

how to save values in core data in swift

I have to apply checkmarks on the contacts received by using address book. When I'm selecting contacts then checkmarks appears but after scrolling it disappears. Secondly, I have to save these selected contacts into core data. Just have a look at that and tell me what I'm doing wrong.What Wrong i'am doing with Core Data.
class LogItem: NSManagedObject {
#NSManaged var section: String
#NSManaged var keys: String
}
Now declare the object of this class like this:
let newItem = NSEntityDescription.insertNewObjectForEntityForName("LogItem", inManagedObjectContext: self.managedObjectContext!) as! LogItem
newItem.section = "section Title"
newItem.keys = "keys text"
}
You can fetch the data as follows:
// request using the LogItem entity
let fetchRequest = NSFetchRequest(entityName: "LogItem")
// Execute the fetch request, and cast the results to an array of LogItem objects
if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [LogItem] {
println(fetchResults[0].section) // prints "section title"
println(fetchResults[0].key) // prints key text
}
Please Make sure that core data is able to save only properties.
According to apple documentation, “The Core Data framework provides
generalized and automated solutions to common tasks associated with
object life-cycle and object graph management, including persistence.”
First, you add CoreData, then go to AppDelegate and you will see this code:
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Model_data")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
In: let container . . . you see (name: "Model_data")
This name needs to be same as your model file name.
Then go to your View Controller and add:
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let newItem = Item(context: self.context)
Item is an entity from your model object.
add newItem to an array like:
self.itemArray.append(newItem)
Then, add next logic:
fileprivate func saveContext() {
do {
try context.save()
// if it is table view, you need to reload here. tableView.reloadData()
} catch {
print("Failed to with error: \(error)")
}
}
if loading is problem add this logic:
fileprivate func loadContext() {
let request: NSFetchRequest<Item> = Item.fetchRequest()
do {
itemModel = try context.fetch(request)
} catch {
print("Error in request: \(error)")
}
}
Save and load.