I am presently making my first steps using Core Data, with Swift.
Even though I have quite a bit of experience with Core Data, I have close to none with Swift.
Here is what I want to do: make a function which is going to compute the number of records in an entity, from the name of the entity.
Something like this:
func countEntity (name:String) -> Int {
var theNumberOfRecords:Int
let fetchRequest = NSFetchRequest(entityName:name)
// What I have tried at this point only gives error messages …..
return theNumberOfRecords
}
It should not be rocket science. But I have not been able to make something work after various trial and errors.
How should I write the necessary code? I presume it should be no more that a few lines. The one I have in Objective C is 6 lines.
You get the number of objects a fetch request would have returned
with countForFetchRequest():
func countEntity (name:String, context : NSManagedObjectContext) -> Int {
let fetchRequest = NSFetchRequest(entityName:name)
var error : NSError?
let theNumberOfRecords = context.countForFetchRequest(fetchRequest, error: &error)
if theNumberOfRecords == NSNotFound {
println("Error: \(error?.localizedDescription)")
}
return theNumberOfRecords
}
In the case of an error, countForFetchRequest() returns
NSNotFound and you have to decide how to handle that situation.
Related
Update: I looked up core data validation(validateForInsert() on managedObhects) upon suggestion but somehow it is not catching the uniqueness constraint violations. I don’t know why because it looks like it should be pretty straightforward. I tried it on both the superclass managedObject type and particular entity types, on both parent and child contexts(on corresponding objects in each context) but nothing works. Although, I only tried it on UIManagedDocument so far. Validation sounds like a much better option but I’m getting frustrated I can’t make it work and haven’t got a clue why.
——
A while back I wrote a func to create default name for core data entities. Little did I know how much trouble it was getting me into… The first iteration of the func can be found in another (solved)question if you are interested(you don’t have to read that question to understand this one): < Generics method with predicated core data search won’t return correct result >
The first iteration was working sort of ok. However, it didn’t consider if a gap exists between the numbers of existing default names. For example, if name, name1 and name3 exist simultaneously in database, a repetition of name3 would be created, and it won’t satisfy where names has to be unique.
So to solve that, I thought I should add a while-loop to check the uniqueness of the newly created names and increase count by 1 in each loop until it was indeed unique. This won’t always fill in the gap, but I just want the names to be unique. I’ll provide the code below but first, the problem I encountered was that it acts “funky.” Sometimes the name is nil when it shouldn’t(no error was caught) or an incorrect name that already exists. So my question is what is the problem with my code?Or can I do to find out what is the problem? Or maybe is there a better way to check for uniqueness other than checking uniqueness error by saving contexts? (Maybe should just fetch and count result instead?) Am I approaching this default name function wrong? I would appreciate any input and discussion.
Here are my codes. The setup is a UIManagedDocument, which automatically provides two contexts, a child and a parent. The parent is a background one. I’m only using the child for edit right now, but I did setup and check both for throwing uniqueness errors. I also set up uniqueness constraints for the attributes.(for simplicity, I omitted the error type I defined and the error type casts related to that.)
func checkUniqueness()throws {
var mergeError: Error? = nil
do{
try childContext.save()
childContext.parent!.performAndWait{
do{
childContext.parent!.save()
}catch{
mergeError = error
print(“parent error”)
}
}catch{
mergeError = error
print(“child error”)
}
if mergeError != nil{
if (mergeError! as NSError).code == 133021 && (mergeError! as NSError).domain == NSCocoaErrorDomain{
throw someUniquenessErrorIDefined
}else{
throw someDataErrorIDefined
}
}
}
//can throw an error indicating func failed and name is set to nil
func setDefaultName<T>(entity:T, defaultString: String, attribute: String) throws {
try? context.save()
//initial values
var name = defaultString
var count = 0
var someError: Error? = nil
//fetch the count
let type = T.self
let fetchRequest = NSFetchRequest<NSNumber>(entityName: “\(type)”)
let pred = NSPredicate(format: “%K CONTAINS[cd] %#", attribute, defaultString)
fetchRequest.predicate = pred
fetchRequest.resultType = .countResultType
do {
let result = try context.fetch(fetchRequest)
count = result.first!.intValue
}catch{
throw someDataErrorIDefined
}
//
//loop to find unique value
repeat{
do{
if count != 0{
name = defaultString + String(count)
}
(entity as! NSManagedObject).setValue(name, forKey: attribute)
count += 1
try checkUniqueness()
someError = nil
}catch{
someError = error
}
} while someError == someUniquenessErrorIDefined
if someError == someDataErrorIDefined{
(entity as! NSManagedObject).setValue(nil, forKey: attribute)
throw someError
}
}
I should add that the funky behaviors disappeared after I relaunched Xcode and I have not been able to reproduce them, but I’m just worried they will come up again. Maybe I shouldn’t check uniqueness this way at all? I just read that uniqueness constraints are the more performant way to insure uniqueness, but I’m not sure they are supposed to be used like this.
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
I new to Swift programming. I am using CoreData in my app as a database option.
I am using NSManagedObject (deleteObject) to delete lastObject, firstObject and entire rows in the table.
I want to delete a specific row in the table by using a value which is stored in a variable. I am not using table view (I don't have any index path), this delete operation is the background process after I get response from the my application server.
Any links or tutorials or suggestions will be helpful.
Thanks in advance!!!
Code for deleting lastObject
if var results1 = context.executeFetchRequest(requestParams, error: nil) as? [Params] {
println("\n Params Results count : \(results1.count)")
for param in results1{
for var n = 0 ;n < results1.count; ++n
{
let lastPerson = (results1 as NSArray).lastObject as Params
context.deleteObject(lastPerson)
var savingError: NSError?
context.save(&savingError)
}
}
}
Params table consists of columns 'pname' and 'pvalues'.
I want to delete a specific param pname ='server_timeout'.
Consider if 'Params' table consist of 10 rows and 'pname'='server_timeout' might be present at index 5, 6.
How can I find the 'pname' (i.e 'server_timeout') in this list of records and delete it?
How about creating a predicate that filters out the list to those with pname value of server_timeout in the first place.
context.performBlock {
requestParams.predicate = NSPredicate(format: "pname == %#", "server_timeout")
if let results = context.executeFetchRequest(requestParams, error: nil) as? [Params] {
for param in results {
context.deleteObject(param)
}
var error: NSError?
if !context.save(&error) {
println(error)
}
}
}
As you can see, I have placed the whole search-and-delete inside a performBlock closure. I don't know how you have setup your Core Data, but you might want to consider threading to maximize perceived performance.
I am assigning non-nil values to entities, saving them, and then immediately retrieving them. But the values come back as nil. This is iOS 8.1 and Swift on Xcode (latest GA release) with identical behavior on both simulator and a device.
I have three entities: Match, Score and Team. Each Match has two teams, team1 and team2. Score also has team1 and team2. Match has a 1x1 relationship with Score, the fields being bet and betFor respectively. Match is identified by its field matchNumber.
So I have the following code:
var match:CBMatch?
var betCard:CBScore?
for matchNum in 1...42 {
match = CBMatch.lookup(self.managedObjectContext!, matchNumber: matchNum)
betCard = CBScore.addForBet(self.managedObjectContext!, match: match!)
betCard!.team1 = match!.team1
betCard!.team2 = match!.team2
match!.bet = betCard!
println("For match \(matchNum), \(betCard!.team1.shortname) vs \(betCard!.team2.shortname)")
// This is fine for all matchNum values
}
// Verify
if true {
var err:NSError? = nil
self.managedObjectContext!.save(&err)
if err != nil {
abort() // Hasn't happened yet
}
match = CBMatch.lookup(self.managedObjectContext!, matchNumber: 1)
betCard = match!.bet
println("For match 1, \(betCard!.team1.shortname) vs \(betCard!.team2.shortname)")
// Crashes with NPE
// It complains that betCard!.team2 is nil but betCard!.team1 is fine
}
Basically an attempt to retrieve the information I just set comes back as nil.
CBMatch:lookup and CBScore:addForBet are trivial:
class func lookup(moc: NSManagedObjectContext, matchNumber: Int) -> CBMatch? {
var fetchRequest = NSFetchRequest(entityName: "CBMatch")
var predicate = NSPredicate(format: "matchNumber == %d", matchNumber)
fetchRequest.fetchLimit = 1
fetchRequest.predicate = predicate
// fetchRequest.returnsObjectsAsFaults = false
// With or without didn't make a difference
var err:NSError? = nil
let matches = moc.executeFetchRequest(fetchRequest, error: &err)!
if matches.count == 1 && err == nil {
let fetchedMatch = matches[0] as CBMatch
return fetchedMatch
}
println("Could not find match \(matchNumber)")
return nil
}
class func addForBet(moc: NSManagedObjectContext, match:CBMatch) -> CBScore {
var entity = NSEntityDescription.insertNewObjectForEntityForName("CBScore", inManagedObjectContext: moc) as CBScore
entity.betFor = match
return entity
}
I cannot simplify the data model by simply discarding bet.team1 etc, since there will be situations where bet.team1 and match.team1 will be different and I need to be able to compare them. For this (simplest) case they are identical, as this is the first time Score objects are being initialized.
I have tried several things, including returnsObjectsAsFault = false during the fetch etc. When I open the .sqlite file using sqlite3, I can see the entries being made. I tried using sql debug flag set to 3, but nothing jumped out. I am not saying that avenue is exhausted, but I definitely am.
Any thoughts on what could be going on here? I am stuck on this for quite some time and any thoughts would be appreciated.
The error is EXC_BAD_ACCESS, with the problem that match.bet.team1 is nil . This is the same assignment I did barely a few lines above.
It seems to me that there is a flaw in your data model. You are referencing each team twice, which is really not necessary. The Bet entity does not need to know about the teams, as it has a to-one relationship with the Match entity. Bet could contain something along the lines of firstValue and secondValue.
While theoretically your setup should also work, you are introducing unnecessary complexity. You are not giving enough information to troubleshoot your error, but I think it is not necessary to even bother hunting down that bug.
Simply use match.team1 instead of match.bet.team1.
BTW, the obvious thing to look out for is that either match is nil, score is nil, or match.team1 is nil - in all cases score.team1 is also nil.
Found the bug in my code - the team1 and team2 relationships were 1:1, but in order for allowing multiple allocations they needed to be 1:N.
I am stuck on this code! I want to remove the optionals what is the best way to do that?
I tried to change the AnyObject in to NSString but I don't know how.
What is the best way to do this?
And my second question is how do I store this in the core data? Is there a cool framework for this?
Alamofire.request(.GET, urlDb)
.responseJSON { (request, response, json, error) in
println(error)
//println(json)
if let contactGroups : AnyObject! = json{
//println("Contact groepen")
var contactGroups = contactGroups["contact_groups"] as NSArray
for cG in contactGroups {
println(cG["group_id"]!)
//println(cG["contact_id"]!)
}
}
if let contacts : AnyObject = json{
//println(contacts["contacts"])
println("Contacten")
var contacts = contacts["contacts"] as NSArray
for c in contacts{
println(c["id"])
println(c["name"])
}
}
}
This is the console log
nil
Optional(1)
Optional(2)
Contacten
Optional(1)
Optional(Dirk Sierd de Vries)
I hope you guys can help me with my school project :D
Modify for loop like below:
for c in contacts {
println(c["id"] as NSString)
println(c["name"] as NSString)
}
Explanation:
In Swift you have to specify object to specific type. If you not than it will by default showsOptional keyword. Here your dictionary key values are directly printed on log and you have not specified that which type of value stored in dictionary.
About Saving in Core Data:
No there isn't any cool framework which can save data into core data. You need to do it your self. Below link is best to follow to start learning this.
Swift + Core data