I want to access the Realm data file which is created on iOS Simulator. With the instruction of this question, I got to the following directory:
~/Library/Developer/CoreSimulator/Devices/0AB2D954-A3D5-4ABF-A5C5-CC470FB4ABDE/data/Containers/Data/Application/7192723E-7582-48EB-8B66-14E1073D7CE6/Documents
However, the directory has only the following Realm files:
default.realm.note
default.realm.log_a
default.realm.log_b
default.realm.log
default.realm.lock
and I cannot find the Realm file default.realm.
Also, when I open up the Realm Browser and access to the directory, all the files above except default.realm.note is grayed-out and I cannot access the Realm data.
Note that I create a Realm instance and save data by realm.write:
try! self.realm.write {
for (_, value) in json { // json is from SwiftyJSON
let pitcher = Player()
player.year.value = value["year"].int
player.name = value["name"].stringValue
self.realm.add(player)
}
}
So how can I access the Realm data file?
Realm's auxiliary files (.note, .lock, etc) are always created next to the .realm file itself. So it seems like something is deleting the realm file without deleting the auxiliary files, which would explain why you can't find it.
Please make sure you're not deleting this Realm file somewhere in your code.
Related
Performing migrations on a Realm database is poorly documented and the documentation seems to be out-of-date. There are two areas explaining how to migrate data:
-- The simple example on the Realm website: https://realm.io/docs/swift/latest/
-- A more detailed example in the Github examples section: https://github.com/realm/realm-cocoa/blob/master/examples/ios/swift-3.0/Migration/AppDelegate.swift
Neither of these examples adequately explain how to migrate data between schema versions. I've tried playing around with the examples and have yet to get any migrations working. As well, I've had problems with app crashes when upgrading to newer Realm versions without schema changes and data changes, which don't occur in the Simulator but occur when installing the app from TestFlight or the App Store.
Seems like the Realm documentation and examples detailing migrations are due for a refresh. My areas of interest are:
Upgrading to a newer Realm version without a schema change in the database. Unclear whether I should continue using the default.realm file generated with a previous version, or whether I need to re-generate the default.realm file using the newer Realm framework version.
Adding a new attribute to a Realm object.
New objects ("rows") added to an existing class without any schema change.
No schema changes to existing classes in the database, but addition of an entirely new class or classes.
Any combination of the above.
Thanks!
Sorry the docs are not sufficient. We appreciate the feedback and will use it to improve them. In the mean time, let me answer your questions:
You do not need to do anything when you upgrade the SDK. Sometimes, we upgrade the core database file format, but this migration happens automatically when you open the Realm (Realm()) so you don't have to worry about it.
When you add a new property to an object you can just follow this code snippet.
Nothing is needed in the migration block since this block is simply to apply data transformations between versions. All you need to do is increment the schemaVersion
// Inside your application(application:didFinishLaunchingWithOptions:)
let config = Realm.Configuration(
// Set the new schema version. This must be greater than the previously used
// version (if you've never set a schema version before, the version is 0).
schemaVersion: 1,
// Set the block which will be called automatically when opening a Realm with
// a schema version lower than the one set above
migrationBlock: { migration, oldSchemaVersion in
// We haven’t migrated anything yet, so oldSchemaVersion == 0
if (oldSchemaVersion < 1) {
// Nothing to do!
// Realm will automatically detect new properties and removed properties
// And will update the schema on disk automatically
}
})
// Tell Realm to use this new configuration object for the default Realm
Realm.Configuration.defaultConfiguration = config
// Now that we've told Realm how to handle the schema change, opening the file
// will automatically perform the migration
let realm = try! Realm()
Adding objects to a Realm does not affect the schema so a migration is not relevant.
This is the same as 2, you simply need to increment the schemaVersion but you don't have to do anything in the migration block since Realm handles everything. The migration block is for custom migration logic where, for example, you want to transform firstName and lastName from schemaVersion=0 to fullName when updating to schemaVersion=1. In this case, you could get the data from the old version and concatenate the strings into the new fullName property within the migration block.
Hope this helps!
Unfortunately, that doesn't work, at all. There is a block of code I had to add to iOS apps several months ago in order to get Realm to work, but it doesn't solve the migration problems I've experienced:
config.fileURL = config.fileURL!.deletingLastPathComponent().appendingPathComponent("default-v1.realm")
Realm.Configuration.defaultConfiguration = config
// copy over old data files for migration
//let defaultURL = Realm.Configuration.defaultConfiguration.fileURL!
let defaultURL = Realm.Configuration.defaultConfiguration.fileURL!
//print("Realm.Configuration.defaultConfiguration.fileURL! = \(defaultURL)")
//let defaultParentURL = defaultURL.URLByDeletingLastPathComponent
if let realmURL = bundleURL("default-v1") {
do {
//print("Bundle location for default.realm = \(realmURL)")
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let url = URL(fileURLWithPath: path)
let filePath = url.appendingPathComponent("default-v1.realm").path
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath) {
print("FILE INSTALLED")
print("Documents location for default.realm = \(filePath)")
} else {
//print("FILE NOT INSTALLED, COPYING default.realm from bundle to Documents directory.")
try FileManager.default.copyItem(at: realmURL, to: defaultURL)
}
} catch {}
}
I suspect that a new default-v1.realm file may clobber an existing default-v1.realm file on a user's device, using this code with the "Nothing is needed" migration method.
Part of the confusion stems from the example migration code at https://github.com/realm/realm-cocoa/tree/master/examples/ios/swift-3.0/Migration where data from the v1 file is getting copied over to the v2 file. Regardless, I've tried the "Nothing is needed" code you've posted and the app doesn't find any Realm data then crashes.
It looks like I could splice the code you've posted into the code I've been using then perhaps that would solve the problem. Will look into that next week.
Thanks for your response!
I'm trying to get my iPhone app to load text from a file into a string array, with 1 line from the file per array element.
I've created an input file as a text file using sublime text. I dragged the file (which is located inside of a folder inside of my project directory) into xCode into a folder in the same location in the project heirarchy.
I also tried adding it as a bundle (by copying the folder and renaming it with the .bundle extension), to no avail. Currently, my app has the file in 2 places (Obviously I plan to delete the unneeded version, but I'm not sure how this will work so I've left it for now).
I've written a function that I want to read my file, and assemble its contents into an array:
func readFromFile(filename: String) -> [String]? {
guard let theFile = Bundle.main.path( forResource: fileName, ofType: "txt") else {
return nil // ALWAYS returns nil here: Seems 'filename' can't be found?????
}
do { // Extract the file contents, and return them as a split string array
let fileContents = try String(contentsOfFile: theFile)
return fileContents.components(separatedBy: "\n")
} catch _ as NSError {
return nil
}
}
As it stands, the function always returns nil at the location commented in the code.
I've been working on this for ~6hrs (and tried every suggestion I could find on StackOverflow, google etc) and I'm just getting more and more confused by the differences between the various versions of Swift and intricacies of iOS development. I can't seem to find a consistent answer anywhere. I've checked the apple documentation but it's too high level with no example code for me to understand at my swift beginner level.
I also tried naming the file with a ".txt" extension but that didn't help either.
The file must certainly be named alert01.txt if you are going to refer to it as forResource: "alert01", ofType: "txt".
Loading from a bundle will not work. The file needs to be part of your project as shown in the first entry.
However, your code is not going to work because you have created a folder reference. That means the folder PanicAlertFiles is being copied with all its contents into your bundle. Your code will need to dive into that folder in order to retrieve your file. Use path(forResource:ofType:inDirectory:) to do that, or (if you don't want to have to code the file name explicitly) get the folder and then use the FileManager to examine its contents.
I am trying to use a bundled realm file without success. I know that my realm file was copied successfully to my application’s Directory but I ca not read it.
fatal error: 'try!' expression unexpectedly raised an error: "Unable
to open a realm at path
'/Users/…/Library/Developer/CoreSimulator/Devices/…/data/Containers/Data/Application/…/Documents/default-v1.realm'.
Please use a path where your app has read-write permissions."
func fullPathToFileInAppDocumentsDir(fileName: String) -> String {
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory,NSSearchPathDomainMask.UserDomainMask,true)
let documentsDirectory = paths[0] as NSString
let fullPathToTheFile = documentsDirectory.stringByAppendingPathComponent(fileName)
return fullPathToTheFile
}
In didFinishLaunchingWithOptions:
let fileInDocuments = fullPathToFileInAppDocumentsDir("default-v1.realm")
if !NSFileManager.defaultManager().fileExistsAtPath(fileInDocuments) {
let bundle = NSBundle.mainBundle()
let fileInBundle = bundle.pathForResource("default-v1", ofType: "realm")
let fileManager = NSFileManager.defaultManager()
do {
try fileManager.copyItemAtPath(fileInBundle!, toPath: fileInDocuments)
} catch { print(error) }
}
And setting the configuration used for the default Realm:
var config = Realm.Configuration()
config.path = fileInDocuments
Realm.Configuration.defaultConfiguration = config
let realm = try! Realm(configuration: config) // It fails here!!! :-)
As the documentation suggests, I have tried as well to open it directly from the bundle path by setting readOnly to true on the Realm.Configuration object. I am not sure if this is something related to Realm or if I am overlooking something with the file system…. I have also tried to store the file in the Library folder.
Realm 0.97.0
Xcode Version 7.1.1
I tried to open the realm file using Realm's browser app and the file does not open anymore. It has now new permissions: Write only (Dropbox). So, I decided to change the file permission back to read/write using file manager’s setAttributes method. Here is how I did it:
// rw rw r : Attention for octal-literal in Swift "0o".
let permission = NSNumber(short: 0o664)
do {
try fileManager.setAttributes([NSFilePosixPermissions:permission], ofItemAtPath: fileInDocuments)
} catch { print(error) }
The realm file can now be open at this path.
That exception gets thrown whenever a low level I/O operation is denied permission to the file you've specified (You can check it out on Realm's GitHub account).
Even though everything seems correct in your sample code there, something must be set incorrectly with the file location (Whether it be the file path to your bundle's Realm file, or the destination path) to be causing that error.
A couple of things I can recommend trying out.
Through breakpoints/logging, manually double-check that both fileInDocuments and fileInBundle are being correctly created and are pointing at the locations you were expecting.
When running the app in the Simulator, use a tool like SimPholders to track down the Documents directory of your app on your computer, and visually ensure that the Realm file is being properly copied to where you're expecting.
If the Realm file is in the right place, you can also Realm's Browser app to try opening the Realm file to ensure that the file was copied correctly and still opens properly.
Try testing the code on a proper iOS device to see if the same error is occurring on the native system.
If all else fails, try doing some Realm operations with the default Realm (Which will simply deposit a default.realm file in your Documents directory), just to completely discount there isn't something wrong with your file system
Let me know how you go with that, and if you still can't work out the problem, we can keep looking. :)
This will occur if you have your realm file open in Realm Studio at same time you relaunch your app. Basically in this case Realm can't get write permissions if Studio already has them.
To add to the solution based on what I've discovered, make note of what error Realm reports when it throws the exception, as well as the type of Error that is passed.
As of writing, Realm documents their errors here:
https://realm.io/docs/objc/latest/api/Enums/RLMError.html
What this means is that you can find out if your Realm file has permissions problems and react to those based on Realm passing you a RLMErrorFilePermissionDenied. Or if the file doesn't exist with RLMErrorFileNotFound.
The tricky thing I'm finding is when you get a more generic RLMErrorFileAccess, but that's for another question on Stack Overflow...
I had the same issue and tried too many ways to fix it. The easiest way to fix this problem is manually creation of the folder XCode cannot reach '/Users/…/Library/Developer/CoreSimulator/Devices/…/data/Containers/Data/Application/…/Documents/...' as explained at https://docs.realm.io/sync/using-synced-realms/errors#unable-to-open-realm-at-path-less-than-path-greater-than-make_dir-failed-no-such-file-or-directory
Once you created this folder and run the project, XCode creates the Realm files inside this folder automatically.
so I have some realm swift code:
let realm = Realm()
realm.writeCopyToPath(tempfile! , encryptionKey: nil)
let newasset = CKAsset(fileURL: NSURL(fileURLWithPath: tempfile!, isDirectory: false))
and then I save off the newasset to cloudkit...
but when I bring it back,
let defaultPath = Realm().path
var someError: NSError?
NSFileManager.defaultManager().removeItemAtPath(defaultPath, error: &someError)
if someError != nil {
println("Error removing realm file, \(someError)")
}
NSFileManager.defaultManager().copyItemAtPath(asset.fileURL.path!, toPath: defaultPath, error: &someError)
if someError != nil {
println("Error copying realm file into place, \(someError)")
}
it does not seem to work... I'm wondering if there is another way to bring back the realm file and place it where realm expects it?
The default location and file name of the default Realm file is default.realm, located in the app's Documents directory. As long you've correctly copied the Realm file out of CloudKit and to that location with the proper file name, accessing that file via calling Realm() should just work straight out of the box.
It sounds like you've got a few points in here that you can potentially test to see if anything is failing:
1) Make sure the duplicate Realm file is being created properly.
2) Ensure the file is uploaded to CloudKit properly, without fail.
3) Ensure the file came back down from CloudKit properly without any errors.
4) Double-check that copying the file from CloudKit to the default Realm file path succeeded.
If that all didn't work, please let me know here what sort of error you're specifically getting. Thanks, and good luck!
I'm making an app that makes use of a big database with 5 different tables. I want to import that database to the app bundle and be able to query through the tables. The database won't be edited by the user, so adding and removing records is not required.
What would be the best way to add the database in the app?
The process is as follows:
Add the database to your bundle. When you drag it into your project, you can choose to add it to the target that represents your main app. Alternatively, review the target settings, click on "Build Phases" and confirm the database appears in the "Copy Bundle Resources" list.
Use a framework like FMDB to simplify your life. This is written in Objective-C, but works great in Swift, too. What you need to do is:
Copy the .h and .m files for FMDB into your project;
When prompted to create a "bridging header", do so;
Add the following line to your bridging header:
#import "FMDB.h"
By following those steps, you can use this framework developed in Objective-C into your Swift projects.
You can now write your Swift code, using the FMDB framework. For example, the Swift code to open the database, select columns x, y, and z from a table called test, would look like:
let path = NSBundle.mainBundle().pathForResource("test", ofType:"sqlite")
let database = FMDatabase(path: path)
if !database.open() {
print("Unable to open database")
return
}
if let rs = database.executeQuery("select * from test", withArgumentsInArray: nil) {
while rs.next() {
let x = rs.stringForColumn("x")
let y = rs.stringForColumn("y")
let z = rs.stringForColumn("z")
print("x = \(x); y = \(y); z = \(z)")
}
} else {
print("executeQuery failed: \(database.lastErrorMessage())")
}
database.close()
Swift only. No Objective-C files required.
Here is another solution which uses SQLite.swift instead of FMDB.
1.: Get a valid path to your database file
Don't save the database file in your Assets folder, but add it to your Copy Bundle Resources list in the Build Phases setting of your current target. If your file is called myDb.db you can then get a valid path like this:
let dbUrl = Bundle.main.url(forResource: "myDb", withExtension: "db")!
let dbPath = dbUrl.path
2.: Access your database (without copying it)
It is possible to access your database now without having to (manually?) copy it. Just use the SQLite.swift library:
db = try! Connection(dbPath)
Notes:
The only thing I haven't checked yet explicitly is writing to the database. At least a read-only access does work like a charm though.