iOS Realm readOnly causes empty result - ios

I have a little problem with realm setting readOnly to true in the configuration. I am using Swift but I am using the normal Realm.framework ... Maybe this causes the error but I don't think so. (I am using it because I don't want to refactor my whole code...)
Here is an example of what I am talking about.
I tried this:
if let path = NSBundle.mainBundle().pathForResource("database", ofType: "realm")
{
//Alternative: or move the file from the app bundle into a writable location
var config = RLMRealmConfiguration()
config.path = path
config.readOnly = true
do {
try self.realm = RLMRealm(configuration:config)
} catch {
print("ERROR")
}
I also tried this:
if let path = NSBundle.mainBundle().pathForResource("database", ofType: "realm")
{
//Alternative: or move the file from the app bundle into a writable location
var config = RLMRealmConfiguration()
config.path = path
config.readOnly = true
RLMRealmConfiguration.setDefaultConfiguration(config)
do {
try self.realm = RLMRealm(configuration: RLMRealmConfiguration.defaultConfiguration())
} catch {
print("ERROR")
}
Both of them fail every time. (Printing: "Error") I figured out that the problem is that readOnly is set to true. I got both snippets working by setting readOnly to false.
Do you have any idea how to fix this issue?
I printed the Error and it is:
Error Domain=io.realm Code=2 "Unable to open a realm at path
'/Users/UserName/Library/Developer/CoreSimulator/Devices/NR/data/Containers/Bundle/Application/NR/AppName.app/database.realm'"
UserInfo={NSFilePath=/Users/UserName/Library/Developer/CoreSimulator/Devices/NR/data/Containers/Bundle/Application/NR/AppName.app/database.realm,
NSLocalizedDescription=Unable to open a realm at path
'/Users/UserName/Library/Developer/CoreSimulator/Devices/NR/data/Containers/Bundle/Application/NR/AppName.app/database.realm',
Error Code=2}

Okay guys I have no idea what happened but it works now.
I made a sample project with the database of their database and an object for their database and tried my code and it worked. (Both of them)
This confused me pretty much and I thought maybe my database is damaged or the object is wrong. I replaced their database in the sample project with my database and used my object and surprise it worked...
So I just deleted my database in my normal project (as I did 1000 times before) and added it again. I just put it on the top of my project structure and tried it again and it is working now...

Related

Using an existing database in an iMessage app (Swift)

Currently building an iMessage app, and would like to experiment with using a database. I have a database that I would like to use in the app, and have included it in my project, and verified the target membership is correct. Using SQLite.Swift.
Whenever I try opening the connection to the database in simulator, I always get an error (unexpected nil) for the path of the database.
I've tried an image file the same way with no avail.
let imagePath = Bundle.main.path(forResource: "db", ofType: ".sqlite")
do {
let db = try Connection(imagePath!, readonly: true)
} catch {
}
I believe the issue is more related to what an iMessage "app" is - which is actually an extension, not a true app. There's no initial VC, thus no real Bundle.main to get to.
One (maybe soon a second) app of mine has a Photo Editing Extension - basically what I always have called a "shell connection" to an Apple app. You really have either a "do nothing" app with a connection to one of their apps, or you have a stand-alone app an share the code with the extension.
My solution for sharing code is to use a Framework target. Yes, a third project. (App, extension, shared code.) I found a technique that I think should work for you - basically, for images, scripts (my apps use .cikernel files) you add them into the framework project and return what you need in a function call.
You may be able to streamline this with a need for a Framework target. YMMV. The basics are this:
Someplace in Xcode you have a "Bundle Identifier". Something like *"com.company.projectname".
Put your files into a folder, maybe on your desktop. Add an extension to this folder called ".bundle". macOS will give you a warning, accept it. All you are really doing is creating your bundle.
Drag this into your Xcode project.
Code to get to this bundle, and the files inside it. (I'm not sure if need a framework here - try to drag this into your "MessagesExtension" target first.
So lets say you have images you wish to share between projects, extensions, whatever. After moving them into a folder called "images", andrenaming the folder with a ".bundle" at the end, and finally dragging it into your Xcode project, you pretty much need to add this function:
public func returnImage(_ named:String) -> UIImage {
let myBundle = Bundle.init(identifier: "com.company.project")
let imagePath = (myBundle?.path(forResource: "images", ofType: "bundle"))! + "/" + named
let theImage = UIImage(contentsOfFile: imagePath)
return theImage!
}
For a text file you want:
public func returnKernel(_ named:String) -> String {
let myBundle = Bundle.init(identifier: "com.company.project")
let kernelPath = (myBundle?.path(forResource: "cikernels", ofType: "bundle"))! + "/" + named + ".cikernel"
do {
return try String(contentsOfFile: kernelPath)
}
catch let error as NSError {
return error.description
}
}
Usage, for an image called "Camera.png" which is part of a bundle called "images.bundle":
let cameraImage = returnImage("Camera")
Since I don't work with SQLite files I don't have the exact code, but I think this should work. Remember to change "com.company.project" to what you have for the bundle identifier.

Realm migration inadequately documented. Can anyone clarify?

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!

RealmSwift: Realm at path already opened with different schema version

I've recently created a new branch and attempted refactoring much of my code to give Realm a shot over CoreData. But so far I haven't had any luck getting my code to run.
First, an exception is thrown in shared_realm.cpp. The line of code that throws the error is:
if (realm->config().schema_version != config.schema_version && config.schema_version != ObjectStore::NotVersioned) {
throw MismatchedConfigException("Realm at path already opened with different schema version.");
}
If I skip over this exception, it catches on the second line of code in the following:
class func getAllCategories() -> Results<Category> {
let realm = try! Realm()
let categories = realm.objects(Category)
return categories
}
And throws this error message:
fatal error: 'try!' expression unexpectedly raised an error: Error
Domain=io.realm Code=1 "Realm at path already opened with different
schema version." UserInfo={NSLocalizedDescription=Realm at path
already opened with different schema version., Error Code=1}
I am completely new to Realm, so any help is appreciated. My understanding from the documentation is that Realm() is the proper way to access the default database, which is fine for my purposes at the moment. At first I thought that perhaps a Realm had to be passed around, but I see from online examples that this doesn't appear to be the case.
I have cleaned, changed simulators, and even updated Xcode. I also attempted to comment this line of code back in:
// FIXME - enable schema comparison
/*if (realm->config().schema != config.schema) {
throw MismatchedConfigException("Realm at path already opened with different schema");
}*/
to no avail. Feeling quite lost, so any direction is appreciated.
The schema version for a path can't be changed after it has been opened, so you'll need to change the schema before calling the path with setSchemaVersion.
setSchemaVersion(1, realmPath: Realm.defaultPath) { (migration, oldSchemaVersion) -> Void in
if oldSchemaVersion < 1 {
migration.enumerate(Category.className(), { (oldObject, newObject) -> Void in
let constant = oldObject!["constant"] as! String
newObject!["constant"] = constant
})
}
}
You might be running into this problem, because you changed the schema after you already built the app once (just a guess, you can confirm this by deleting the app and rebuilding it, which also clears the existing realm database.)
If that is indeed the problem, you should look into
https://realm.io/docs/swift/latest/#migrations
which outlines the recommended way to fix this issue.
For me the issue was that I added a new field as a String like this :
schema.get("RealmCompany").addField("ctype", String.class);
When I declared it in the model class as an Integer.
I had to do :
if (oldVersion == 7) {
Log.i(TAG, "migrate: " + 7);
schema.get("RealmCompany").removeField("ctype");
schema.get("RealmCompany").addField("ctype", Integer.class);
}

Unable to open a realm at path

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.

realm.io writeCopyToPath ? what's the inverse operation?

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!

Resources