How do I Create objects and save with core data in for loop instead of overwriting each time? - swift

I am trying to use a for loop to create objects, which have attributes I will use later to populate a map. I am able to loop through and populate the attributes of the object, but when I save the object, it seems to be overwriting each time. the for loop pulls data out of my firebase database.
let holes = [hole1,hole2,hole3,hole4,hole5,hole6,hole7,hole8,hole9,hole10,hole11,hole12,hole13,hole14,hole15,hole16,hole17,hole18]
for hole in holes {
let holeX = hole?["HoleX"] as? Double // these are just grabbing the data for the appropriate hole from my firebase data.
let holeY = hole?["HoleY"] as? Double
let holeH = hole?["HoleH"] as? Double
var newHole = NSEntityDescription.insertNewObject(forEntityName: "HoleCoordinateData", into: self.managedObjectContext)
newHole.setValue(holeX, forKey: "HoleX")
newHole.setValue(holeY, forKey: "HoleY")
newHole.setValue(holeH, forKey: "HoleH")
newHole.setValue(holeNumber, forKey: "holeNumber")
newHole.setValue("\(holeNumber)", forKey: "holeNumberString")
self.managedObjectContext.insert(newHole)
holeNumber += 1
}
do {
try self.managedObjectContext.save()
} catch {
print("Could not save Data: \(error.localizedDescription)")
}
I have tried the do try catch within the for loop. I originally had the newHole being initialized prior to the for loop, but I put it inside on the advice of some other stack questions and responses. I originally filled out newHole by just doing newHole.holeX = holeX. obviously some of these changes were just different ways of doing the same thing, but I've been spinning my wheels on this for long enough to try anything I could.

Related

Firebase Checking If Username is Taken [duplicate]

Okay I am reading from a database and when I print the individual variables they print out correctly. However it seems like the data refuses to append to the array. Anyone know why? I can't figure it out at all.
let commuteBuilder = Commutes()
Database.database().reference().child("Users").child(user).child("Trips").observe(DataEventType.childAdded, with: { (snapshot) in
//print(snapshot)
if let dict = snapshot.value as? NSDictionary {
commuteBuilder.distance = dict["Distance"] as! Double
commuteBuilder.title = dict["TripName"] as! String
commuteBuilder.transportType = (dict["Transport"] as? String)!
}
commuteArray.append(commuteBuilder)
})
print("helper")
print(commuteArray.count)
return commuteArray
The data is correctly added to the array, just not at the time that you print the array's contents.
If you change the code like this, you can see this:
let commuteBuilder = Commutes()
Database.database().reference().child("Users").child(user).child("Trips").observe(DataEventType.childAdded, with: { (snapshot) in
if let dict = snapshot.value as? NSDictionary {
commuteBuilder.distance = dict["Distance"] as! Double
commuteBuilder.title = dict["TripName"] as! String
commuteBuilder.transportType = (dict["Transport"] as? String)!
}
commuteArray.append(commuteBuilder)
print("added one, now have \(commuteArray.count)")
})
print("returning \(commuteArray.count)")
return commuteArray
You'll see it print something like this:
returning 0
added one, now have 1
added one, now have 2
etc.
This is likely not the output you expected. But it is working as intended. Firebase loads data from its database asynchronously. Instead of blocking your code, it lets the thread continue (so the user can continue using the app) and instead calls back to the code block you passed to observe when new data is available.
This means that by the time this code returns the array it is still empty, but it later adds items as they come in. This means that you cannot return data from a function in the way you are trying.
I find it easiest to change my way of thinking about code. Instead of "First get the data, then print it", I frame it as "Start getting the data. When data comes back, print it".
In the code above, I did this by moving the code that prints the count into the callback block. Instead of doing this, you can also create your own callback, which is called a completion handler or closure in Swift. You can find examples in this article, this article, this question Callback function syntax in Swift or of course in Apple's documentation.

Multiple async request in nested for loops

I'm trying to get data from my database and after obtaining a piece of data, using that piece of data to find a new piece of data.
At the end, I can piece these together and return the derived data. I'm not sure this is the best way to approach this, but this is where im at as of now.
My problem is that each call to the database (Firebase) is async and therefore I need to somehow wait for the async to finish, before going on.
I've looked at dispatch group and heres what I have so far:
let taskGroup = DispatchGroup()
for buildingKey in building.allKeys
{
var aprt = NSDictionary()
taskGroup.enter()
// ASYNC REQUEST
getAbodesWithUID(UID: buildingKey as! String, callback: { (success, abodes) in
aprt = abodes
taskGroup.leave()
})
taskGroup.enter()
for key in aprt.allKeys
{
// ASYNC REQUEST
getTenantsWithAprt(UID: key as! String, callback: { (success, user) in
for userKey in user.allKeys
{
let dict = NSMutableDictionary()
dict.setValue(((building[buildingKey] as? NSDictionary)?["Address"] as? NSDictionary)?.allKeys[0] as? String, forKey: "Building")
dict.setValue((user[userKey] as? NSDictionary)?["Aprt"], forKey: "Number")
dict.setValue((user[userKey] as? NSDictionary)?["Name"], forKey: "Name")
dict.setValue(userKey, forKey: "UID")
dict.setValue((user[userKey] as? NSDictionary)?["PhoneNumber"], forKey: "Phone")
apartments.append(dict)
}
taskGroup.leave()
})
}
}
taskGroup.notify(queue: DispatchQueue.main, execute: {
print("DONE")
callback(true, apartments)
})
I can't seem to get it to callback properly
First, you should be iterating over aprt.allKeys inside of the callback for getAbodesWithUID, other wise, when the for loop executes aprt will be an empty dictionary.
Secondly, the taskGroup.enter() call above that for loop should be inside of the for loop, because it needs to be called once for every key. It should be placed where the // ASYNC REQUEST comment currently is.
This is precisely what "promises" are for is for. They are available in Swift via a 3rd party add-in. A popular way to do this is to push all your reads/gets into an array. Then you promise.all(yourArray) which returns the array of results/values that you then iterate over to get at each one.
From this other answer:
You can look into when which may provide what you need and is covered
here.
Use the loop to put your promises into an array and then do something
like this:
when(fulfilled: promiseArray).then { results in
// Do something
}.catch { error in
// Handle error
}

xcode. swift3. coredata. fatal error: Index out of range

I keep getting this error when trying to load an array xputt2 from coreData. When I remove the offending line the data prints perfectly but will not load into the array once I put the line back. Does anyone know what I am doing wrong? Here is the code.
do {
putt2Array = try managedObjectContext.fetch(fetchRequest)
print ("results count is", putt2Array.count)
var i=0
for record in putt2Array {
print(record.value(forKeyPath: "missed")! as! Double)
xputt2[i] = record.value(forKeyPath: "missed")! as! Double
i += 1
}
} catch let error as NSError{
print("Could not fetch data. \(error), \(error.userInfo)")
}
}
The error explains the problem, index is out of range, you are keying into an array at an index that does not yet exist. Try switching
xputt2[i] = record.value(forKeyPath: "missed")! as! Double
to
xputt2.append(record.value(forKeyPath: "missed")! as! Double)
You should be able to remove the i = 0 and incrementing i. On a separate note, I would recommend removing the forced unwraps:
for record in putt2Array {
if let value = record.value(forKeyPath: "missed") as? Double {
xputt2.append(value)
}
}
An array's index only goes as far as the number of elements currently in it. You can use xputt2[i] to replace an element that already exists, but not to add a new one. You can either use xputt2.append to add it to the end, or xputt2.insert if you want to put it at a particular place in the array.
Alternately, since you're just transforming an existing array, you can use the map method to do it in a single line:
xputt2 = putt2array.map{ $0.value(forKeyPath: "missed")! as! Double }

Problems importing large file in Core Data with background thread

I am running into some trouble importing a large .csv file in OS X using Core Data on a background thread.
My simplified data model is a Dataset which has a to-many relationship to many Entries. Each entry is a line in the .csv file (which has a bunch of attributes that I'll leave out for brevity). Following what I have read about efficiently importing lots of data, along with how to make a progress indicator work properly, I have created a managed object context for the purposes of going the import.
I am having trouble with two things:
I need to hang on to a reference to the new Dataset at the end of the import, because I need to select it in the popup. this will be done in the main thread, but for efficiency (and to make my NSProgressIndicator work) the new dataset is created on the background thread, with the background MOC.
From what I read, batching the import, so that the background MOC saves and resets, is the best way to stop the import from eating up too much memory. That does not turn out to be the case so far - it looks like gigs of memory is being used even for files in the tens of megabytes. Also, once I reset my import MOC, it cannot find the data for the inDataset, and so cannot create the relationship between all subsequent Entries and the Dataset.
I've posted the simplified code below. I have tried refreshObject:mergeChanges, without good results. Can anyone point me at what I am doing wrong?
let inFile = op.URL
dispatch_async(dispatch_get_global_queue(priority, 0)) {
//create moc
let inMOC = NSManagedObjectContext()
inMOC.undoManager = nil
inMOC.persistentStoreCoordinator = self.moc.persistentStoreCoordinator
var inDataset : inDataset = Dataset(entity: NSEntityDescription.entityForName("Dataset", inManagedObjectContext: inMOC)!, insertIntoManagedObjectContext: inMOC)
//set up NSProgressIndicator here, removed for clarity
let datasetID = inDataset.objectID
mocDataset = self.moc.objectWithID(datasetID) as! Dataset
let fileContents : String = (try! NSString(contentsOfFile: inFile!.path!, encoding: NSUTF8StringEncoding)) as String
let fileLines : [String] = fileContents.componentsSeparatedByString("\n")
var batchCount : Int = 0
for thisLine : String in fileLines {
let newEntry : Entry = Entry(entity: NSEntityDescription.entityForName("Entry", inManagedObjectContext: inMOC)!, insertIntoManagedObjectContext: inMOC)
//Read in attributes of this entry from this line, removed here for brevity
newEntry.setValue("Entry", forKey: "type")
newEntry.setValue(inDataset, forKey: "dataset")
inDataset.addEntryObject(newEntry)
dispatch_async(dispatch_get_main_queue()) {
self.progInd.incrementBy(1)
}
batchCount++
if(batchCount > 1000){
do {
try inMOC.save()
} catch let error as NSError {
print(error)
} catch {
fatalError()
}
batchCount = 0
inMOC.reset()
inDataset = inMOC.objectWithID(datasetID) as! Dataset
// fails here, does not seem to be able to find the data associated with inDataset
}
}// end of loop for reading lines
//save whatever remains after last batch save
do {
try inMOC.save()
} catch let error as NSError {
print(error)
} catch {
fatalError()
}
inMOC.reset()
dispatch_async(dispatch_get_main_queue()) {
//This is done on main queue after background queue has read all line
// I thought the statement just below would refresh mocDataset, but no luck
self.moc.refreshObject(mocDataset, mergeChanges: true)
//new dataset selected from popup
let datafetch = NSFetchRequest(entityName: "Dataset")
let datasets : [Dataset] = try! self.moc.executeFetchRequest(datafetch) as! [Dataset]
self.datasetController.addObjects(datasets)
let mocDataset = self.moc.objectWithID(datasetID) as! Dataset
//fails here too, mocDataset object has data as a fault
let nDarray : [Dataset] = [mocDataset]
self.datasetController.setSelectedObjects(nDarray)
}
}

delete past save entries of core data in swift

I have a problem where i cannot completely delete data stored in core data entities. i am storing the username and password in a login system.
i am aware of using the .deleteObject() method but to be honest im not sure if this is actually working as i do a print statement to check in console what is stored and it appears to have not been deleted
here is my code:
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context: NSManagedObjectContext = appDelegate.managedObjectContext
//add user to core data
let user = NSEntityDescription.insertNewObjectForEntityForName("User", inManagedObjectContext: context)
do{
try context.save()
}catch{
print("there was an error saving to core data")
}
do{
let request = NSFetchRequest(entityName: "User")
let results = try context.executeFetchRequest(request)
if results.count > 0{
for item in results as! [NSManagedObject]{
let username = item.valueForKey("username")
let password = item.valueForKey("password")
context.deleteObject(item)
print("aawawaw \(username), \(password)")
}
}
}catch{
print("there was an error")
}
You are printing the values that are stored in the let constants .
Try to implement in for loop
context.deleteObject(item)
let username = item.valueForKey("username")
let password = item.valueForKey("password")
print("aawawaw \(username), \(password)")
You did not really delete the objects from CD.
You should:
delete the object.
store the context
then perform a new fetch on the entity
print all instances of the entity
What you actually do is different:
store the context (with which changes whatever, but noting related to your question)
fetch the entity
delete object from the CD context
print the object that is still around although detached from CD.
as far as t. You are doing:
we can see, you don't save anything and threfore don't save the deleted state.