I am trying to use a sqlite database in one of my projects.
It was working fine; but for a reason, something happened and I couldn't find that bug.
The resultSet object always quit from after the first record.
The array always has only 1 record in it. (Probably it left the while because of the error)
I created a DBManager class, and this DBManager class contains different inner Classes. I have a private global FMDatabase instance (And I initialise it somewhere before using it)
As you see, there are 2 different print error line
When I run, the second print line gives this error:
Error calling sqlite3_step (21: out of memory) rs
Error Domain=FMDatabase Code=7 "out of memory" UserInfo=0x790308d0 {NSLocalizedDescription=out of memory}
And the array which should contain over 300 records, has only 1 record in it.
(Last print line is always 1)
This part is looking simple. (I have totally similar code somewhere else, but it works fine)
private var database : FMDatabase!
class DBManager{
class Element{
class func get()->[DataElement]{
database.open()
println( database.lastError() )
var result = [DataElement]()
var resultSet: FMResultSet! = database!.executeQuery("SELECT * FROM Element WHERE working = 1", withArgumentsInArray: nil)
while resultSet.next( ) {
let data = DataElement(
id : Int(resultSet.intForColumn("id")),
name: resultSet.stringForColumn("name"),
server: resultSet.stringForColumn("server"),
working: resultSet.boolForColumn("working") )
result.append( data )
}
println( database.lastError() )
database.close()
println( result.count )
return result
}
}
}
PS: Only difference between those tables is (as I realize ) the "id" column. This Element table has an "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, but the other one does not have any id column. But both of them worked well for a long time.
The "out of memory" error is a misleading SQLite error that means that a SQLite function was called with a NULL value for the sqlite3* pointer. In FMDB this means that you closed the database but then tried to continue using the same FMDatabase instance (without calling open again).
Now, I don't see you doing that in this code sample, but this code sample is employing some practices that make that sort of error possible. Namely, rather than instantiating the FMDatabase object locally, you are using some database property and you run the risk that some other function may have used it (maybe the init method for DataElement? maybe some other function that you removed for the sake of brevity? maybe some other thread?).
Let's imagine that this function called some other function that opened the database again (which FMDB will silently let you do, basically returning immediately if the database is already open), perform some SQL, and then closed the database, then this routine, when it went to retrieve the second row of information, would of have found the database closed, resulting in the error you describe. The opening and closing of the database object is not recursive. Once the subroutine closed it, it's completely closed.
Now all of this is hypothetical, because without seeing what the rest of your code does, it's impossible for me to confirm. But its a scenario that would fit what code you have shared combined with the the symptoms you've described.
Assuming that this is really the case, there are two possible solutions here:
You could simply open the database once and leave it open until the app is terminated. This is probably the easiest approach and is probably more efficient than what you have here. SQLite is actually pretty robust in terms of committing individual SQL statements (or transactions), so people usually leave the database open.
I might even go step further, and suggest that if you might have different threads interacting with this database, that you instantiate a single FMDatabaseQueue object, and use that throughout the app, again not opening and closing all the time. Note, if you use FMDatabaseQueue, you'll want to be even more judicious about making sure that one function that is in the middle of an inDatabase or inTransaction block doesn't call another function that tries to do another inDatabase or inTransaction block (or else you'll deadlock). But like any shared resource, you want to be sensitive to where a resource is locked and when it's released, and databases are no exception to that rule.
If you're determined to open and close the database in every function like this code sample suggests (again, a practice I wouldn't advise), then do not use a class property to keep track of the database. Sure, have some function that opens the database, but return this FMDatabase object, and each function would have it's own local instance and would close that instance when it's done with it.
But you really want to avoid the unintended consequences of one function's closing the database affecting the behavior of some other function.
i also faced the same issue, as after deleting the records from the table i am closing the connection and after some time i am calling one more sql query, at that point of time i got the same issue
"FMDatabase Code=7 "out of memory" UserInfo=0x790308d0 {NSLocalizedDescription=out of memory}"
just add one line of code before closing the connection ie.,
database = nil
database.close()
Later add the method in your modal class as per below
func openDatabase() -> Bool {
if database == nil {
if !FileManager.default.fileExists(atPath: pathToDatabase) {
let documentsDirectory = (NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString) as String
pathToDatabase = documentsDirectory.appending("/\(databaseFileName)")
print(pathToDatabase)
}
database = FMDatabase(path: pathToDatabase)
}
if !database.isOpen {
database.open()
}
return true
}
Before executing any query check this,
if openDatabase() {
//Query
}
It solved my issue.
Related
This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed last year.
I am building a mobile app with swift, and am having some syntax issues as I am not a developer. The structure and logic of the application is really rough and surely incorrect, however we just need something that functions. (It is a school project and my team got no devs).
Anyways, we have a MySQL database that will be used as a middleman between our badge server/admin app, and our mobile app. Currently when you go to https://gatekeeperapp.org/service.php , you will see the current database data, taken by a php script and hosted there as JSON. Currently in Swift I have a struct with a function that takes this JSON data, and maps it to variables. The idea is to then pass these pulled variables into a separate set of functions that will check the pulled long/lat against the mobile devices location, and then return whether they match or not. This value would be updated, re-encoded to JSON, and pushed to a web service that would go about changing the values in the database so the badge server could use them.
Where I am currently I can see that values are being pulled and mapped and I can set a variable in a separate function to the pulled value, but then I can only seem to output this value internally, rather than actually use it in the function. I get a type error saying that the pulled values are of type (). How can I properly use these values? Ultimately I think I would want to convert the () to a double, so I could properly compare it to the Long/Lat of the device, and then will need to re-encode the new values to JSON.
Swift Code -- struct function
Swift code -- JSON struct
Swift code -- using pulled data
Your closure is called asynchronously, which means that the outer function where you are expecting to use the values has already returned by the time the closure is called. Instead, you probably need to call some other function from the closure, passing the values you've received.
class MyClass {
func fetchUserData() {
UserData().fetchUser { [weak self] user, error in
DispatchQueue.main.async {
if let user = user {
self?.handleSuccess(userID: user)
} else if let error = error {
self?.handleError(error)
}
}
}
}
private func handleSuccess(userID: String) {
print(userID)
// Do something with userID. Maybe assign it to a property on the class?
}
private func handleError(_ error: Error) {
print(error)
// Handle the error. Maybe show an alert?
}
}
So my goal is to get rid of these bugs completely. I am in a dilemma where each decision leads to a bug.
The first thing I can do that eventually becomes an issue is use a String-interpolated collection path in all my query functions like so:
func getEventName() {
listener = db.collection("school_users/\(user?.uid)/events").order(by: "time_created", descending: true).addSnapshotListener(includeMetadataChanges: true) { (querySnapshot, error) in
if let error = error {
print("There was an error fetching the data: \(error)")
} else {
self.events = querySnapshot!.documents.map { document in
return EventName(eventName: (document.get("event_name") as! String))
}
self.tableView.reloadData()
}
}
}
The thing with this is, when I run the app on the simulator, I am restricted from pressing buttons and then sometimes I can press them and then sometimes they get restricted again. This bug is so confusing because it makes no sense where it springs from.
The other issue is I can use a Constants value in all the query functions in my collections path.
static let schoolCollectionName = "school_users/\(user?.uid)/events"
This is nested in a Firebase struct within the Constants struct. In order to keep Xcode from giving errors I create a let users = Auth.auth().currentUser variable outside the Constants struct. The issue with this value is that when I put that in all of my query functions collection paths, all the buttons are accessible and selectable all the time, but when a user logs out and I log in as a new user, the previous user's data shows up in the new user's tableview.
It would obviously make more sense to use the Constants value because you prevent typos in the future, but I can't figure out how to get rid of the bug where the old user's data shows up in the new user's tableview. Thanks in advance.
The user id should definitely not be a constant. What it sounds like is that right now, you have no reliable way to change users -- your setup probably depends on which user is logged in at app startup, since that's where your variable gets set.
I would do something more like this:
func getEventName() {
guard let user = Auth.auth().currentUser else {
//handle the fact that you don't have a user here -- don't go on to the next query
return
}
listener = db.collection("school_users/\(user.uid)/events").order(by: "time_created", descending: true).addSnapshotListener(includeMetadataChanges: true) { (querySnapshot, error) in
Note that now, user.uid in the interpolated path doesn't have the ? for optionally unwrapping it (which Xcode is giving you a warning for right now). It will also guarantee that the correct query is always made with the currently-logged-in user.
Regarding being able to press the buttons, that sounds like an unrelated issue. You could run your app in Instruments and check the Time Profiler to see if you have long-running tasks that are gumming up the main/UI thread.
After updating XCode to version 11 I added a new model version to Core Data and in new version I added a new attribute to an Entity. Made the new version active and added the new property to managed object file.
After releasing this version to the users it started to crash with the following message: "The managed object model version used to open the persistent store is incompatible with the one that was used to create the persistent store." and "duplicate column name ZNEWCOLUMN". Until now I made a lot of changes to the Core Data model and migration always worked.
This crash appears only on iOS 13!
This is how I load Core Data:
lazy var managedObjectContext: NSManagedObjectContext = {
return self.persistentContainer.viewContext
}()
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = NSPersistentContainer(name: "MyModel")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
let description = NSPersistentStoreDescription()
description.shouldInferMappingModelAutomatically = true
description.shouldMigrateStoreAutomatically = true
container.persistentStoreDescriptions.append(description)
return container
}()
Any help would be appreciated.
The same thing is happening to me, lightweight migration at iOS 12 was right at real device and Simulator but at iOS 13 fail with the next log result:
SQLite error code:1, 'duplicate column name: ZNAME_OF_THE_COLUMN .... Error Domain
= NSCocoaErrorDomain Code = 134110 "An error occurred during persistent storage migration."
I load data like #iOS Dev post.
I check the xxxx.sqlite database file in the emulator path before and after the migration and there were no columns with those new same names.
To know the route of the *.sqlite in emulator you have to put a breakpoint and when it is stopped put in the console po NSHomeDirectory().
Then go to Finder window, tap the keys Control + Command + G and paste the route. Yo can handle it (for example) with DB Browser for SQLite program, it´s free.
After a long search I have seen what has happened to some people but I have not seen any solution.
Mine was:
Select the actual *.xcdatamodel.
Select Editor > Add Model Version.
Provide a version name based on the previous model (like XxxxxxV2.xcdatamodel).
Click on this new version model NewV2.xcdatamodel.
Select this new version as Current on Properties at right hand of IDE.
Make your changes at DDBB.
Run app and will work fine.
I did tests overriding app (with new values) and it was fine.
I hope this may help.
If you want to edit the descriptions, you need to do so before you load the stores (and I have no idea what appending a new description would do):
container.persistentStoreDescriptions.forEach { storeDesc in
storeDesc.shouldMigrateStoreAutomatically = true
storeDesc.shouldInferMappingModelAutomatically = true
}
container.loadPersistentStores { [unowned self] (storeDesc, error) in
if let error = error {
// handle your error, do not fatalError! even a message that something is wrong can be helpful
return
}
// do any additional work on your view context, etc.
}
If your problem is reproduceable, you should look at the error that's being returned and look for something called ZNEWCOLUMN (though this sounds like a temporary default name?) This nomenclature is the raw column name in the SQL database though, so it's likely the migrator is attempting to add this new column and failing.
Try turning on SQL debugging in your scheme's Arguments:
-com.apple.CoreData.SQLDebug 1
Try logging into the raw SQL database (the above will give you the raw path if you're on the simulator). Try rolling back to the previous data model on a previous OS and then just upgrading to 13.
Sounds like you have some duplicate column somewhere so these are just some ideas to find out where it is.
I have an iOS app which stores it's data in a Core Data database. The basic structure has Libraries and Items, with a many-to-many relationship between them represented by LibraryItemMapping. The mapping item contains an index, so that items can be sorted differently in different containers. This works fine most of the time, but on my own device which I use for dev/test, I am running into situations where the Core Data objects are returning nil for one side or the other of those relationships. If I enumerate those objects at launch (via code in AppDelegate), the nils disappear and I'm able to retrieve objects on either side of the relationship, but when looking for the same relationships just a few seconds later they begin to return nil. This, for example, always runs OK and never errors out or finds any nils:
public func runConsistencyCheck() {
do {
let mappings = try dataContext.fetch(NSFetchRequest<LibraryItemMapping>(entityName: "LibraryItemMapping"))
for map in mappings {
if (map.item==nil || map.library==nil) {
print("Found corrupt mapping record. Deleting.")
dataContext.delete(map)
}
}
saveContext()
} catch {
print("An error occurred during consistency checking: \(error)")
}
}
But this regularly finds nil for obj.item and/or crashes on my device:
func getMapping(forItem item: Item) -> LibraryItemMapping? {
if (libraryData?.itemMappings != nil) {
for obj in libraryData!.itemMappings as! Set<LibraryItemMapping> {
if (obj.item != nil && obj.item! == item) { return obj }
else {
print("Found Core Data mapping with null item data.")
}
}
}
return nil
}
Attempting to create or modify and then save any other records results in multiple iterations of this error:
"Error Domain=NSCocoaErrorDomain Code=1570 \"item is a required value.\" UserInfo={NSValidationErrorKey=item, NSLocalizedDescription=item is a required value., NSValidationErrorObject=<LibraryItemMapping: 0x174098060> (entity: LibraryItemMapping; id: 0xd0000000001c0000 <x-coredata://ED9F1AE5-A7D0-46FD-AC94-941E9EFEF341/LibraryItemMapping/p7> ; data: {\n index = 4;\n item = nil;\n library = \"0xd000000000140004 <x-coredata://ED9F1AE5-A7D0-46FD-AC94-941E9EFEF341/LibraryData/p5>\";\n})}"
This only happens on the one device that I am aware of, so I'm tempted to put it down to a database corrupted by a bad debug session at some point, but at the very least I'd like to be able to clean up after it gracefully. If this were SQL, I would just run a consistency check and something like delete from LibraryItemMapping where item is null or library is null and move on, but I'm not sure how to do the equivalent with Core Data. How can I troubleshoot this further, and how can I gracefully recover from a seemingly corrupt database?
After pulling the sqlite files from the device and digging through the raw data, this actually turned out to be a data model bug. During app startup it goes through a "prune old records" step, and inconsistent delete rules on the relationships for the linking table allowed it to leave records in an inconsistent state. After correcting the delete rules to use Cascade or Nullify as appropriate, everything is working again.
I am using Firebase iOS framework. Don't see an obvious way to tell the build version I am using. I am testing on an iOS simulator usually.
I implemented a helper function to find certain data by indexed key. I have created indexes on these keys in the security rules, but that should not be necessary. My data and query are very similar to the dinosaur query by height example in the firebase docs. My data is flattened to //{key: value, key: value, ...}. E.g., Player/-JwISoamh_jRhYeKvKLk will contain a dictionary like:
{
"height": "1.89",
"firstName": "LeBron",
"lastName": "James"
}
I use my code to find all players with lastName === "James". So, in the code below the ObjBase would point to /Player and childKey = "lastName" and value="James". I expect querySnaphot to contain 1 child with the node for "JwISoamh_jRhYeKvKLk".
Even though the data is in my test data in my app on firebaseio.com and the values match up, this code returns me a querySnapshot.childrenCount == 0:
FQuery *query = [[objBase queryOrderedByChild:childKey] queryEqualToValue:value];
[query observeSingleEventOfType:FEventTypeValue withBlock:^(FDataSnapshot *querySnapshot) {
Until I change the FEventTypeValue to FEventTypeChildAdded. At that point I get data, but it is not an array of nodes in the querySnapshot.children, but values. As soon as I change the code back to FEventTypeValue my query, exactly as it was before, will work for some period of time (multiple runs of the app). Possibly it stops working when I nuke the data for a bunch of new changes. Maybe it is being cached locally after that initial load.
Perfectly willing to believe I am doing something wrong, but I cannot see what and the fact that it works after I "seed it" with the ChildAdded leads me to think it is an async issue or some initial setup I need to do.
Thanks!!