When trying to downcast a fetched NSManagedObject to it's correct subclass StoryPhotoTrack in my (Swift) unit tests, I get a runtime error: Could not cast value of type 'StoryPhotoTrack_StoryPhotoTrack_' (0x7fbda4734490) to 'StoryPhotoTrack' (0x11edd1b08).
Here's my failing / crashing test:
func testFetchingStoryWithCastingInvolved() {
let story : StoryPhotoTrack? = storyFetcher.fetchAnyStoryPhotoTrack()
XCTAssertNotNil(story, "should be a story") // works
let definitlyAStory : StoryPhotoTrack = story! // works
let object : NSManagedObject = story as NSManagedObject! // works
println(NSStringFromClass(definitlyAStory.dynamicType)) // prints 'StoryPhotoTrack_StoryPhotoTrack_'
let downCastedStory : StoryPhotoTrack = object as! StoryPhotoTrack // -> error: "Could not cast..."
XCTAssertNotNil(downCastedStory, "should still be a story")
}
// StoryFetcher.swift
func fetchAnyStoryPhotoTrackVia_ObjectiveC_Fetcher() -> StoryPhotoTrack? {
let object = StoryPhotoTrack.fetchAnyStoryPhotoTrackInContext(moc)
return object as StoryPhotoTrack?
}
The actual fetching is done in Objective-C:
// StoryPhotoTrack+Fetching.m
+ (StoryPhotoTrack *)fetchAnyStoryPhotoTrackInContext:(NSManagedObjectContext *)moc
{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"StoryPhotoTrack"];
NSArray *storyTracks = [moc executeFetchRequest:request error:nil];
return (StoryPhotoTrack *)[storyTracks lastObject];
}
I assume, it has something to do with Swift's namespaces. Seems like my StoryPhotoTrack class is actually called StoryPhotoTrack_StoryPhotoTrack_.
I tried renaming the class names of my core data entities with projectName-prefixes (as suggested here), but that did not help. I tried both my main target module name and my test target module name, none of them worked.
What am I doing wrong?
edit: seems like I overlooked SO questions like How to test Core Data properly in Swift, so it seems, this issue is known, but not easy to solve. Is it really the best way to create a separate framework for your Core Data Model?
I had the same issue, for some reason, I was not able to cast to my custom NSManagedObjects for unit test, the only solution was to keep the result set as NSManagedObject Array and access it is fields as below without casting it to ProductModel, object in itself is still a ProductModel but casting it cause an error.
I kept my model name as ProductModel without MyAppName.ProductModel.
func testProductModel() {
let resultFetchRequest = NSFetchRequest(entityName: ProductModel.Name)
let productModels: [NSManagedObject] = context.executeFetchRequest(resultFetchRequest, error: &error) as!
[NSManagedObject]
if let recipes = productModels[0].valueForKey("recipes") as? NSSet {
let recipesCount = recipes.count;
XCTAssertTrue(recipesCount == 0)
} else {
XCTFail("FAIL - no recipes nsset")
}
}
Related
I'm fairly new to CoreData, and I'm trying to make a game. I have a couple of questions I was hoping you guys could help me out with some guidance:
- does GameKit already have some sort of CoreData integrated in it? I am not sure if I am overthinking this CoreData stuff if there's already something that replaces it in GameKit.
. . .
Anyways, assuming the answer to the above question is "no. GameKit has nothing to save your game". I will proceed with my current "Save game" code which is the following:
func saveCurrentMatch()
{
/* CORE DATA STUFF:
FIRST NEED TO VERIFY IF THIS GAME HAS BEEN PREVIOUSLY SAVED, IF SO THEN UPDATE, ELSE JUST SAVE
Entity: MatchData
Attributes: scoreArray (String), playerArray (String), myScore (Int), matchID (Int), isWaiting (Bool), isRealTime (Bool), gameLog (String)
*/
let context = myAppDelegate.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "MatchData")
request.returnsObjectsAsFaults = false
do
{
let gamesInProgress = try context.fetch(request)
print (gamesInProgress.count)
if gamesInProgress.count > 0 //HERE CHANGE THIS TO LOOK FOR THE MATCH ID OF THIS GAME!!
{
gameExistsinCD = true
}
else
{
gameExistsinCD = false
}
}
catch
{
print ("Error Reading Data: \(error.localizedDescription)")
}
if gameExistsinCD
{
//CODE TO UPDATE MATCH INSTEAD OF SAVING NEW ONE
}
else
{
// CODE TO SAVE A NEW MATCH FOR THE FIRST TIME
let matchData = NSEntityDescription.insertNewObject(forEntityName: "MatchData", into: context)
matchData.setValue(isRealTime, forKey: "isRealTime")
matchData.setValue(currentScore?[0], forKey: "myScore")
matchData.setValue(currentScore?.map{String($0)}.joined(separator: "\t"), forKey: "scoreArray") // IS THIS CODE CORRECT? I'M TRYING TO SAVE AN ARRAY OF INTS INTO A SINGLE STRING
matchData.setValue(currentPlayers?.joined(separator: "\t"), forKey: "playerArray")
matchData.setValue(true, forKey: "isWaiting") //will change later to update accordingly.
matchData.setValue(matchID, forKey: "matchID")
matchData.setValue(gameLog, forKey: "gameLog")
do
{
try context.save()
print ("CoreData: Game Saved!")
}
catch
{
print ("Error Saving Data: \(error.localizedDescription)")
}
}
}
My main concern is on the fetch request, how do I check all the core-data if this match has already been saved? and if so, whats the code for updating an Entity instead of inserting a new one?
Any guidance is appreciated, thanks!
Don't let Core Data scare you. It can be a fine way to save local data and despite some comments, it is not slow when done right. In fact, Core Data can be quite fast.
You can simplify your code a lot by using your Object class in a more normal fashion instead of using setValue calls. Your create code can be changed to this:
// CODE TO SAVE A NEW MATCH FOR THE FIRST TIME
if let matchData = NSEntityDescription.insertNewObject(forEntityName: "MatchData", into: context) as? MatchData {
matchData.isRealTime = isRealTime
matchData.myScore = currentScore?[0]
matchData.scoreArray = currentScore?.map{String($0)}.joined(separator: "\t") // IS THIS CODE CORRECT? I'M TRYING TO SAVE AN ARRAY OF INTS INTO A SINGLE STRING
// You can certainly save it this way and code it in and out. A better alternative is to have a child relationship to another managed object class that has the scores.
matchData.playerArray = currentPlayers?.joined(separator: "\t")
matchData.isWaiting = true
matchData.matchID = matchID
matchData.gameLog = gameLog
}
This is a much more readable and normal way to set your object properties. Any time you change a property on a core data managed object then it will get saved the next time you save the context.
As far as finding a current record that matches the ID, I like to add classes like that to my Managed Object class itself:
class func findByID(_ matchID: String) -> MatchData? {
let myAppDelegate = UIApplication.shared.delegate as! AppDelegate
let context = myAppDelegate.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "MatchData")
let idPredicate = NSPredicate(format: "matchID = \(matchID)", argumentArray: nil)
request.predicate = idPredicate
var result: [AnyObject]?
var matchData: MatchData? = nil
do {
result = try context.fetch(request)
} catch let error as NSError {
NSLog("Error getting match: \(error)")
result = nil
}
if result != nil {
for resultItem : AnyObject in result! {
matchData = resultItem as? MatchData
}
}
return matchData
}
Then any place you need the match data by ID you can call the class function:
if let matchData = MatchData.findByID("SomeMatchID") {
// Match exists
}
Core data is basically wrapper around SQL database. It is very efficient when you are working with high volume of data that need to be stored. So please consider either you had such requirements, otherwise perhaps it can be wise to store data in user defaults, or settings.
If it is, there is few things you need to know.
It is very useful to create you own model classes. Open core data model file, open "Editor/Create NSManagedObject subclass". It will allow you to refer direct properties, instead of KVC(setValue:forKey:).
Alway mind what thread you are working in. It is unsafe to work with objects, created in other threads.
Your gamesInProgress contains array of objects you fetched from your database.
So basically instead of
if gameExistsinCD
{
//CODE TO UPDATE MATCH INSTEAD OF SAVING NEW ONE
}
else
{
// CODE TO SAVE A NEW MATCH FOR THE FIRST TIME
let matchData = NSEntityDescription.insertNewObject(forEntityName: "MatchData", into: context)
matchData.setValue(isRealTime, forKey: "isRealTime")
<...>
you can do
let matchData = (gamesInProgress.first ??
NSEntityDescription.insertNewObject(forEntityName: "MatchData", into: context)) as! <YouEntityClass>
matchData.isRealTime = isRealTime
<...>
PS: https://www.raywenderlich.com/173972/getting-started-with-core-data-tutorial-2
In Swift (2.3 currently) I have an NSManagedObject that Ive received through a fetch request, that has a property on it that is contained in a typical Core Data Set. I want to loop through those Core Data objects in the Set, and change properties on them, but Im told that I cannot because they are let constants. Heres my code :
for mediaEntity in aMemory.mediaEntities! {
mediaEntity.remoteId = 0
}
Which gives the error Cannot assign to property : mediaEntity is a let constant
The Core Data property Im trying to change is defined like this :
extension MemoryEntity {
#NSManaged public var mediaEntities: NSSet?
}
Any help much appreciated thanks.
Since you are working with NSSet you need to at least typecast the object. The example I have looks like this:
entity.activities?.forEach { ($0 as? ActivityEntity)?.id = UUID().uuidString }
A more flexible code would then be:
entity.activities?.forEach {
guard let activity = $0 as? ActivityEntity else {
return // Incorrect type. Should never happen
}
activity.id = UUID().uuidString
}
I am using Core Data in my app. I am facing trouble in fetching the results back from coredata. My entity name is Wonders.
Now I managed to successfully save a record. Now when I am fetching it back I am using the following code.
override func viewWillAppear(animated: Bool) {
let wondersAppdel:AppDelegate=UIApplication.sharedApplication().delegate as!AppDelegate
let WondersContext:NSManagedObjectContext = wondersAppdel.managedObjectContext
let wondersFetchRequest = NSFetchRequest(entityName: "Wonders")
wondersFetchRequest.predicate=NSPredicate(format: "wonderShow = %#", true)
let sortDescriptor = NSSortDescriptor(key: "wonderName", ascending: true)
wondersFetchRequest.sortDescriptors=[sortDescriptor]
do {
if
let wonderFetchresults = try WondersContext.executeRequest(wondersFetchRequest) as? [Wonders]{
wonders = wonderFetchresults
}
else{print("else if result...try")}
}
catch{
fatalError("there was an error fatching the list of gruops!")}
self.tableview.reloadData()
}
As of Swift 3.0, you can take advantage of the auto-generated fetchRequest() function on your custom NSManagedObject subclasses:
let request: NSFetchRequest<Wonders> = Wonders.fetchRequest()
let allWonders = try WondersContext.fetch(request)
...and there is no longer any need to cast the result as it will automatically return an array cast to the relevant class (or more accurately, an optional array of the relevant class). Predicates and sort descriptors can be added in the usual way as required, or even programmed directly into the auto-generated fetchRequest() function for the relevant object if there's a default you should fall back on.
You should be calling executeFetchRequest method of NSManagedObjectContext when you are performing a NSFetchRequest.
try WondersContext.executeFetchRequest(wondersFetchRequest)
I'm planning to use Core Data to store my app's data. However, when I tried to design the architecture of the model entities, I found that the most core data tutorials mixed the "fetch data"、"insert data"、"delete data" with ViewControllers. Doesn't such operations be put in model's implementation according to the information system design principle??
For example, there is a base ModelClass:
class BaseModel {
var modelName: String!
// insert
func insertNewObjectToModel(managedContext:NSManagedObjectContext)->BaseModel {
return NSEntityDescription.insertNewObjectForEntityForName(modelName, inManagedObjectContext: managedContext) as! BaseModel
}
// get
func fetchObject(predicate: NSPredicate)...
// delete
func deleteObject(object:BaseModel)...
}
And then I have a model "Student" inherit from BaseModel class, so it inherits its methods of inserting, querying, deleting and so on. Then in ViewController class, if user wants to add a new object, just call student.insertNewObject(context).
I'm really confused about whether such design or encapsulation works. I have read Ray's 《Core Data by Tutorials》, and also read the Apple's Core Data examples. But it seemed that they all put model related operations in ViewController.
For example, I picked a few lines of code from Ray's book:
func populateDealsCountLabel() {
// 1
let fetchRequest = NSFetchRequest(entityName: "Venue")
fetchRequest.resultType = .DictionaryResultType
// 2
let sumExpressionDesc = NSExpressionDescription()
sumExpressionDesc.name = "sumDeals"
// 3
sumExpressionDesc.expression = NSExpression(forFunction: "sum:", arguments: [NSExpression(forKeyPath: "specialCount")])
sumExpressionDesc.expressionResultType = .Integer32AttributeType
// 4
fetchRequest.propertiesToFetch = [sumExpressionDesc]
// 5
do {
let results = try coreDataStack.context.executeFetchRequest(fetchRequest) as! [NSDictionary]
let resultDict = results.first!
let numDeals = resultDict["sumDeals"]
numDealsLabel.text = "\(numDeals!) total deals"
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
}
English is not my tongue language, so I'm not sure if you seeing the question understand what my description want to express. But I'm hoping for your comments and answers! Thanks in advance!
I'm testing swift with CoreData and I created the following code:
import UIKit
import CoreData
class Contact: NSManagedObject {
#NSManaged var name: String
#NSManaged var email: String
class func execute(){
let appDel:AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
let context:NSManagedObjectContext = appDel.managedObjectContext!
let entityDescripition = NSEntityDescription.entityForName("Contact", inManagedObjectContext: context)
let contact = Contact(entity: entityDescripition!, insertIntoManagedObjectContext: context)
contact.name = "john Apleesee"
contact.email = "john#apple.com"
context.save(nil)
let fetch:NSFetchRequest = NSFetchRequest(entityName: "Contact")
var result = context.executeFetchRequest(fetch, error: nil) as [Contact]
let firstContact = result[0] as Contact // <<-- error !
println("\(firstContact.name)")
println("\(firstContact.email)")
}
}
When I run:
Contact.execute()
Then the compiler throw this error:
fatal error: NSArray element failed to match the Swift Array Element type
when I try to get values from array:
let firstContact = result[0] as Contact
I guess the problem is in executeFetchRequest. On Objective-c the return type of executeFetchRequest is a NSArray. Now on Swift the return type is a [AnyObject]?.
I tried to cast the [AnyObject]? result to NSArray, but Xcode said:
Cannot convert the expression's type '[AnyObject]?' to type 'NSArray'
What I'm doing wrong ?
I'm using:
Xcode 6.0 (6A313) GM and
OSX 10.9.4
Update:
-----------
If I get executeFetchRequest result as an optional array and use [NSManagedObject] instead of [Contact] it works.
if let result: [NSManagedObject]? = context.executeFetchRequest(fetch, error: nil) as? [NSManagedObject] {
let firstContact: NSManagedObject = result![0]
let name = firstContact.valueForKey("name") as String
let email = firstContact.valueForKey("email") as String
println("\(name)")
println("\(email)")
}
Update2:
-----------
This kind of problem occurs when entity instances didn't get loaded using your NSManagedObject class. There are a few different reasons why that happens, all of which focus on "Core Data couldn't find the class you specified for that entity on the class line in the data model."
Among the possibilities:
- You specified the wrong package for your Swift class
- You forgot to specify the class
- You misspelled the name
In my case was I forgot to specify the class as POB shows in her answer.
I was able to reproduce your error message in the console. You get this message because you didn't set correctly your entity class name in the Core Data Model Editor (see image below).
Once done, you will be able to use your original code with Contact subClass. You can learn more about Core Data and namespaces here.
In my case, i changed the Module as "Current Product Module" inside Core Data Model Editor.It started working fine for me.
Check the class and module in Entity from Data Model Inspector and set the class name as entity name and module as 'Current Product Module'
Class name and Module
I think its bad idea to set prefix of your target name like this
Because if you would like to move your model into other app, or change targer name you will end up with changing it again,
So there is another way. If you look at how xcode generates core data entities now, as #A_man said you need to set current module in editor and use #objc() like this :
import CoreData
#objc(Contact)
class Contact: NSManagedObject {
#NSManaged var name: String?
#NSManaged var email: String?
}
And field has to be optional because they can be nil
I think your problem is that executeFetchRequest gives back an optional array, not an array. You need to unwrap it.
if let result: [Contact]? = context.executeFetchRequest(fetch, error: nil) as? [Contact] {
let firstContact = result[0]
println("/(firstContact.name)")
println("/(firstContact.email)")
}