I am trying to write a test for my Budget Core Data class to make sure that two budgets with the same name cannot be added. Here is my Budget class.
#objc(Budget)
class Budget: Model {
override func save() throws {
if try exists(name ?? "") {
throw BudgetError.duplicateName
} else {
try save()
}
}
private func exists(_ name: String) throws -> Bool {
let request = Budget.fetchRequest()
request.predicate = NSPredicate(format: "name = %#", name)
request.sortDescriptors = []
let budgets = try viewContext.fetch(request)
return !budgets.isEmpty
}
}
The main issue I am facing is that the test is always failing on the first call to the save function.
func test_Throws_Duplicate_Name_Exception() throws {
var thrownError: Error?
let budget = Budget(context: CoreDataProvider.shared.viewContext)
budget.name = "Car Rental"
CoreDataProvider.shared.viewContext.refreshAllObjects()
try budget.save() <-- TEST FAILS HERE AND THROWS duplicateName EXCEPTION
// save it again
XCTAssertThrowsError(try budget.save()) {
thrownError = $0
}
XCTAssertEqual(thrownError as? BudgetError, .duplicateName)
}
I think the main reason is that when a budget object is created as shown below:
let budget = Budget(context: CoreDataProvider.shared.viewContext)
The budget is also added to the ViewContext and the exists function returns true and the BudgetError is thrown.
What can I do about this situation? The error should happen when I call save the second time, not the first time.
Rather than create first, try retrieving it first, then if it exists, you can modify, otherwise create the new one.
Related
Below is my method in which there is fetch I make on a Managed object Class Appointment. I need to use same function for other similar managed object Classes. How do I pass different "Class" as parameter every time as I need. And also use it to fetch as I have currently "Appointment" Class. I might need to use Generics may be. Dont know how though.
func getAppointmentArray(aPredicate : String , aModel : Any) -> [Any]
{
var apptArr = [Any]()
let fetchRequest = NSFetchRequest<Appointment>(entityName: "Appointment")
fetchRequest.returnsObjectsAsFaults = false
fetchRequest.predicate = NSPredicate(format: aPredicate)
do{
let records = try managedObjectContext.fetch(fetchRequest)
if let records = records as? [NSManagedObject]{
if !records.isEmpty{
print("coreData apptmnt result : \(records)")
var appointment : Appointment?
for obj in records
{
}
}else{
print("No records found")
apptArr = []
}
}
}catch{
print("Error")
apptArr = []
}
return apptArr
}
The good folks at Objc.io provide a really good approach for this. First declare a protocol which inherits 'NSFetchRequestResult' protocol as below.
protocol Managed: class, NSFetchRequestResult {
static var entityName: String { get }
}
Now we can provide a very convenient protocol extension for our protocol 'Managed'.
We do the check 'Self: NSManagedObject' as we want the static method entity() of the NSManagedObject class to get the 'NSEntityDescription' describing the entity associated with our class. Particularly this way we get the entity name dynamically(and conveniently too) for all our ManagedObjects that conform to our protocol.
extension Managed where Self: NSManagedObject {
static var entityName: String { return entity().name! }
}
We now improve the protocol extension by providing a method which conveniently creates a fetch request and then calls a configuration block which might be used to configure the created fetch request by whoever calls it. At the end of this method we do a fetch using the created request.
extension Managed where Self: NSManagedObject {
static var entityName: String { return entity().name! }
//Continued
static func fetch(in context: NSManagedObjectContext, configurationBlock: (NSFetchRequest<Self>) -> ()) -> [Self] {
let request = NSFetchRequest<Self>(entityName: Self.entityName)
configurationBlock(request)
return try! context.fetch(request)
}
}
As you can see we do the following things here:
We make good use of protocols and protocol extensions for making our life easy.
We get the entity name without needing to write a method for each concrete managed object class that we might create. This is reusable for every managed object class that will conform to 'Managed'
The fetch method that we wrote makes use of the dynamic and convenient entityName.
The fetch method again makes use of Self which is implementation independent here. This way we make FetchRequests which are generic in itself.
We provide a convenient way to configure the request to whoever calls this method.
And at atlast we return result after fetching which is also dynamic [Self]
To see our protocol in action we can do this for your case:
class Appointment: NSManagedObject, Managed{
//properties for attributes
//etc...
//Will I get free implementation for entity name and a fetch method
//without writing extra code ?
//Yes why not
}
Testing our hard earned knowledge:
let aPredicate = "......
let context: NSManagedObjectContext.....
let appointments = Appointment.fetch(in: context) { fetchRequest in
//Configuration code like adding predicates and sort descriptors
fetchRequest.predicate = NSPredicate(format: aPredicate)
}
You can use the same pattern for any other ManagedObjects if they conform to the protocol. Eg a Doctor ManagedObject subclass conforming to our Managed protocol:
let aPredicate = "......
let context: NSManagedObjectContext.....
let doctors = Doctor.fetch(in: context) { fetchRequest in
//Configuration code like adding predicates and sort descriptors
fetchRequest.predicate = NSPredicate(format: aPredicate)
}
for the generic you can do something like this:
class FetchingDataHandler<T>{
func getAppointmentArray<T>(forClass : T, aPredicate : String , aModel : Any) -> [Any]
{
}
}
I am writing a generic base class for all of my persistence storage classes. Each child class will work with one specific entity/table in a persistent database using Core Data. The threading appears to be working correctly and I can get the count of items in the table correctly. The problem is that if the entity name in the fetch request is wrong, I do not get an exception, I get a crash. Since this is a string and is typed in somewhere in the code by a programmer, I want to detect the error in some better way so that the programmer is alerted to having used an invalid entity name.
Here's my code:
class Store<EntityType:NSFetchRequestResult> : NSObject {
private var entityName : String = ""
init( entityName : String ) {
self.entityName = entityName
}
public var Count : Int
{
get {
var fetchResults : Int = 0
objc_sync_enter( self )
do {
var privateContext : NSManagedObjectContext? = nil
DispatchQueue.main.sync {
let deleg = UIApplication.shared.delegate as! AppDelegate
privateContext = deleg.privateManagedObjectContext
}
if privateContext == nil
{ return 0 }
privateContext!.performAndWait {
do
{
let request = NSFetchRequest<EntityType>( entityName: self.entityName )
fetchResults = try privateContext!.count( for: request )
} catch
{
print("Unexpected error: \(error).")
}
}
}
objc_sync_exit( self )
return fetchResults
}
}
...
With the wrong entity name, the count() function on the MOC causes a SIGABRT and does not throw any exception.
How do I catch the error somehow?
I am also open to comments about the threading and using this in a background thread. It works now but since the internet and Apple all say vague stuff about how to use Core Data in a background thread, help is appreciated.
I also tried this just now:
let request = NSFetchRequest<EntityType>( entityName: String(reflecting: EntityType.self) )
The name is in the form of "app name.entityname" so it might be usable. But since the editor lets the programmer enter a different name for the entity and for the class, this is not at all safe. If I can somehow check at runtime that the name is valid, I'll use this method. But without solving the crash problem, I'm reluctant to change anything right now.
It's possible to get the list of entity names that exist in the model for the context.
With that, you can check that the entity name supplied by the programmer is valid before executing the fetch request.
//get the context and make sure it's not nil
guard let privateContext = privateContext
else {
print("Unexpected error: context is nil.")
return 0
}
//get the names of entities in the model associated with this context
// credit: Snowman, https://stackoverflow.com/questions/5997586/core-data-list-entity-names
guard let names = privateContext.persistentStoreCoordinator?.managedObjectModel.entities.map({ (entity) -> String? in
return entity.name
})
else {
print("Unexpected error: Could not get entity names from model.")
return 0
}
//make sure the name specified by the programmer exists in the model
guard names.contains(where: { (name) -> Bool in
return name == self.entityName
})
else {
print("Unexpected error: \(self.entityName) does not exist in the model.")
return 0
}
privateContext.performAndWait {
do
{
let request = NSFetchRequest<EntityType>( entityName: self.entityName )
fetchResults = try privateContext.count( for: request )
} catch
{
print("Unexpected error: \(error).")
}
}
If you're wondering about performance: in my testing, retrieving the list of entity names 500 times for a model with 20 entities took 20 ms. Nothing to worry about.
I have been trying to find a way to programmatically create the database entity/table at runtime in Swift 3 for iOS platform.
Please explain the process in detail. I have followed other links but nothing worked for me and I was unable to understand the process properly so please explain the whole process with working code instead of providing other existing links.
There is one more table which has been added from XCode and I can perform the database related operations (insert row, delete, update) on that table but I am unable to find a way to perform following operations on the table which will be created programmatically at run time.
There are following operations that need to be performed.
Add Entity - From interface, user can add entity name, number of columns, column names then on tapping a button the entity should be created into the database using CoreData. User can add any number of tables from the interface.
Insert Values - Then user will have interface to insert values into the entity. The name of the entity will be known and the values for a row in the entity will be entered by user. On tapping a button the row should be inserted into the entity/database-table.
Fetch Values - User will have an option to fetch all the saved values form the entity. To use them anytime when needed.
Update value - User will be able to update any value present in the table which was created programmatically.
Delete Entity - There will be an option to delete the database entity as well. On tapping a button, the name of the entity will be passed to the function and that entity/database-table will be deleted from the database.
I have tried the following code -:
let model = NSManagedObjectModel()
let entityName = "TestEntity"
let entity = NSEntityDescription()
entity.name = entityName
entity.managedObjectClassName = entityName
// Creating a attribute
let remoteURLAttribute = NSAttributeDescription()
remoteURLAttribute.name = "columnOne"
remoteURLAttribute.attributeType = .stringAttributeType
remoteURLAttribute.isOptional = true
remoteURLAttribute.isIndexed = true
var properties = Array<NSAttributeDescription>()
properties.append(remoteURLAttribute)
// Adding the attribute to the entity
entity.properties = properties
// Adding the entity into the model
model.entities = [entity]
do {
try CoreDataManager.managedObjectContext.save()
} catch {
print(error)
}
// Inserting the data into the entity
let testEntity = NSManagedObject(entity: entity, insertInto: CoreDataManager.managedObjectContext)
testEntity.setValue("WHY SO SERIOUS?", forKey: "columnOne")
do {
try CoreDataManager.managedObjectContext.save()
} catch {
print(error)
}
// Fetching the data from the entity
let request = NSFetchRequest<NSFetchRequestResult>()
request.entity = entity
request.propertiesToFetch = ["columnOne"]
request.returnsDistinctResults = false
do {
let results = try CoreDataManager.managedObjectContext.fetch(request)
print(results)
} catch {
}
I am getting the following value in the results -:
▿ 1 element
- 0 : (entity: TestEntity; id: 0x600000422f80
;
data: {
columnOne = "WHY SO SERIOUS?"; })
But I don't have any way to get the values from this entity anywhere else and insert the values into the entity from other places as well by using entity name which is TestEntity in this case.
Some of the steps you describe are simply not possible with Core Data. Or at least, not without making things extremely complex and difficult for you. Asking for complete working code is not reasonable because you have an extremely unusual and complex set of requirements for Core Data.
It's true that the NSManagedModel and all of the entities are mutable and can be changed in code. Except, all of these changes must be made before you use that model to load any data. You cannot make changes after you call loadPersistentStores(_:) (if you're using NSPersistentContainer) or addPersistentStore(ofType:configurationName:at:options:) (if you're not using NSPersistentContainer). Doing so is a run-time error that will crash your app.
Because of this, the idea of providing a user interface to dynamically create and delete Core Data entities is not really an option. You can create and delete instances of entities but not the entity descriptions.
If you're willing to do a lot of hard work, you might be able to get something close. The closest you could get with Core Data would be to create an entirely new managed object model any time you need a change. Then copy all of your data to a new persistent store file using this model (including whatever custom code you need to handle non-lightweight migration). Then delete the old model and persistent store file and all references to all objects that you previously fetched with them.
This will be very difficult to get right and may have unforeseen difficulties that I haven't thought of. I would strongly urge not attempting to do this with Core Data, because it was not designed to be used in this way.
It can be done quit simply with the following code:
import Foundation
import CoreData
class CoreData {
static let shared = CoreData()
let dataBase = NSManagedObjectModel()
let dbTable:NSEntityDescription = {
let tableEntity = NSEntityDescription()
tableEntity.name = "NEntity"
tableEntity.managedObjectClassName = "NEntity"
return tableEntity
}()
// The attributes each item in the table will have. e.g. id, name, password, date
let dbTableAttribute:NSAttributeDescription = {
let tblAttr = NSAttributeDescription()
tblAttr.name = "id"
tblAttr.attributeType = .integer64AttributeType
tblAttr.isOptional = true
return tblAttr
}()
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "APICall", managedObjectModel: dataBase)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
private init(){
dataBase.entities.append(dbTable)
dbTable.properties.append(dbTableAttribute)
}
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
// MARK: - Defining the class for the table, you can put this in a seperate file if you want but for ease of copying I put them together
#objc(NEntity)
public class NEntity: NSManagedObject {
// Variables must be NSManaged to be saved to the database
#NSManaged var id:Int64
// Call this function to save a new item to the table
static func addEntity(_ id:Int) {
let entityName = NSStringFromClass(self)
let moc = CoreData.shared.persistentContainer.viewContext
guard let entity = NSEntityDescription.insertNewObject(forEntityName:entityName, into: moc)
as? NEntity
else { print("Could not find entity: \(entityName) in database: \(CoreData.shared.persistentContainer.name)"); return }
entity.id = Int64(id)
// Update database
CoreData.shared.saveContext()
}
//Call this function to search the database for items matching the id, see removeEntityWithAttribute() for example
func fetchEntityByAttribute(id:Int, completion:#escaping ([NEntity]?) -> ()){
let moc = CoreData.shared.persistentContainer.viewContext
let fetchRequest = NEntity.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "id == %d", id)
moc.perform {
guard let result = try? fetchRequest.execute() else {
return
}
if result.count > 0 {
completion((result as! [NEntity]))
}else {
completion(nil)
}
}
}
//Call this function to delete entities from the database with the matching id
func removeEntityWithAttribute(id:Int) {
let moc = CoreData.shared.persistentContainer.viewContext
fetchEntityByAttribute(id:id) { matchedEntities in
if let matchingEntities = matchedEntities {
matchingEntities.forEach(){
moc.delete($0)
CoreData.shared.saveContext()
}
}
}
}
}
Just call NEntity.addEntity(5) anywhere in your code to save an item in the database with an id of 5. Be sure to give them unique ids so you can edit and delete them.
Sources:
https://tigi44.github.io/ios/iOS,-Swift-Core-Data-Model-in-a-Swift-Package/
https://dmytro-anokhin.medium.com/core-data-and-swift-package-manager-6ed9ff70921a
My application code gets JSON data from a server, converts it to a Dictionary and then uses that to hydrate and attempt to save a RealmSwift object with a matching schema.
I ran into a crash caused by a Float value coming into a field that was declared as Int in the model. RLMException was thrown.
As suggested in this thread, one doesn't simply try to catch RLMException.
My question is, is that answer correct? And, if so, what is the correct way to prevent a crash when an unexpected value is found? Isn't there some sort of validation mechanism that I can run on all of the values before attempting to set them?
You can use third party frameworks for mapping JSON to Realm objects, like ObjectMapper and use it's failable initializer to validate your JSON.
This failable initializer can be used for JSON validation prior to object serialization. Returning nil within the function will prevent the mapping from occuring. You can inspect the JSON stored within the Map object to do your validation.
You can use this to see if everything is ok with json parameters
static func createFromJSONDictionary(json: JSONDictionary) throws -> MyObject {
guard let
property1 = json["property1"] as? Int,
property2 = json["property1"] as? String else {
// Throw error.... or create with default values
throw NSError(domain: "", code: 0, userInfo: nil)
}
// Everything is fine, create object and return it
let object = MyObject()
object.something = property1
return object
}
I ended up writing an extension to handle this. At the moment I'm only validating numbers and dates, but the switch statement could check every single property type easily.
extension Object {
public convenience init( withDictionary rawData: [String:AnyObject] ) {
var sanitizedData = rawData
let dynamicSelf = self.dynamicType
let schema = dynamicSelf.sharedSchema()
for property in schema.properties {
guard let value = sanitizedData[property.name] else { continue }
var isValid = true
switch property.type {
case .Double:
let val = Double(String(value))
if val == nil || val!.isNaN {
isValid = false
}
case .Float:
let val = Float(String(value))
if val == nil || val!.isNaN {
isValid = false
}
case .Int:
if Int(String(value)) == nil {
isValid = false
}
case .Date:
if Int64(String(value)) == nil {
isValid = false
} else {
sanitizedData[property.name] = NSDate(timeIntervalSince1970: value.doubleValue / 1000)
}
default:
break
}
if !isValid {
log.error( "Found invalid value: \(value) for property \(property.name) of \(dynamicSelf)" )
sanitizedData.removeValueForKey(property.name)
}
}
self.init( value: sanitizedData )
}
}
I need to unit test (XCTest) some of my methods that include reference to CoreData models.
The following line execute correctly :
var airport: AnyObject! = Airport.MR_createEntity()
(lldb) po airport <Airport: 0x7fcf54216940> (entity: Airport; id: 0x7fcf54216a20 <x-coredata:///Airport/t1D3D08DA-70F9-4DA0-9487-BD6047EE93692> ; data: {
open = nil;
shortName = nil;
visible = nil; })
whereas the following line triggers an EXC_BAD_ACCESS :
var airport2: Airport = Airport.MR_createEntity() as! Airport
(lldb) po airport2
error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0x0).
The process has been returned to the state before expression evaluation.
No sign of this error with my principal target. The configuration is : model objects in both targets, class prefixed by #objc(MyModel), no namespace in class' models in my xcdatamodel
Any idea what's going on here ?
Right, so I have finally gotten to the bottom of this and its not pretty. There is actually a radar for this issue as it appears to be a bug with the Swift compiler not recognizing ManagedObject casting in test targets. So add your voice to the noise
Starting off with an entity defined as follows:
#objc(Member)
class Member: NSManagedObject {
#NSManaged var name: String
}
I wrote a simple test class in which I create a MO in 3 different ways:
The first two failed:
let context = NSManagedObjectContext.MR_defaultContext()
func testMagicalRecordCreation() {
let m = Member.MR_createInContext(context) as? Member
XCTAssertNotNil(m, "Failed to create object")//fails
}
func testEntityDescriptionClassCreation() {
let m2 = NSEntityDescription.insertNewObjectForEntityForName("Member", inManagedObjectContext: context) as? Member
XCTAssertNotNil(m2, "Failed to create object")//fails
}
And then a success
func testManualMOCreation() {
let ent = NSEntityDescription.entityForName("Member", inManagedObjectContext: context)!
let m3 = Member(entity: ent, insertIntoManagedObjectContext: context)
XCTAssertNotNil(m3, "Failed to create object")
}
This means that right now you have two options. Write your tests in Objective-C; or create a utility method to insert test objects into a context using the means I showed above.
Theres a nice post about this behaviour here
I ended up using an NSManagedObjectContext extension to be used explicitly in Swift Tests:
extension NSManagedObjectContext {
func insertTestEntity<T: NSManagedObject>(entity: T.Type) -> T {
let entityDescription = NSEntityDescription.entityForName(NSStringFromClass(T.self), inManagedObjectContext: self)!
return T(entity: entityDescription, insertIntoManagedObjectContext: self)
}
}
And it could be used like this:
func testConvenienceCreation() {
let m4 = context.insertTestEntity(Member)
XCTAssertNotNil(m4, "Failed to create object")
}
More reading about this kind of approach here
There are two ways to make your Swift application classes available to the test target:
Make your application classes public – this includes every variable, constant, and function you want to test
Add your application Swift file to your Test target. This is pretty simple to do.