I have two realm files in one App, something wrong when I wanna migrate them. I want realm update automatically when run in Xcode while does not change the schemaVersion every time.
class News: Object {
#objc dynamic var name: String
}
class NewsManager {
static var realm = try! Realm()
static var cacheRealm: Realm = {
let documentDirectory = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask,
appropriateFor: nil, create: false)
let url = documentDirectory.appendingPathComponent("cache.realm")
var config = Realm.Configuration()
config.fileURL = url
return try! Realm(configuration: config)
}()
}
When I add a new property to News such as #objc dynamic var title: String,
I add the following code in AppDelegate func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool
let config = Realm.Configuration(schemaVersion: 1, migrationBlock: { migration, oldSchemaVersion in
})
Realm.Configuration.defaultConfiguration = config
The message on crash at return try! Realm(configuration: config) in NewsManager.
Fatal error: 'try!' expression unexpectedly raised an error: Error Domain=io.realm Code=10 "Migration is required due to the following errors:
- Property 'News.test' has been added." UserInfo={Error Code=10, NSLocalizedDescription=Migration is required due to the following errors:
- Property 'News.test' has been added.}: file /BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-900.0.69.2/src/swift/stdlib/public/core/ErrorType.swift, line 181
What should I do?
Realm: 3.0.1
Swift: 4.0
iOS : 10
Realm is working as expected. Some types of model changes Realm can migrate automatically, others require you to provide a manual migration. The one in your example is one of them.
Since you added a new, non-optional property of type String, Realm needs to go through all your existing models and figure out what to put in that property. If the property were type String?, it would make sense to use nil as a default value, and Realm could carry out the migration automatically. However, since the type is String and there's no obvious sensible default, Realm requires you to manually specify a value for the new property for each model object.
The right way to fix this "problem" is to increment your schema number and provide a migration block that actually specifies the new values for new properties whenever you change your model in a way that requires a migration.
Please review our documentation on migrations for more details.
Check it out https://realm.io/docs/swift/latest/#migrations. Realm site is very good for understanding how properly use Realm in your projects
Related
I'm attempting to write some unit tests for my view models that interact with Realm. The logic works fine when run on device / simulator, but triggers a ""RLMException", "Cannot modify managed RLMArray outside of a write transaction" when unit tested.
My test case is as follows...
func testThatNewlyAddedPaymentsAreReturned() throws {
let payment = Payment(recipient: "Matt", amount: Decimal(1.0), date: Date(), note: "")
try model.addPayment(payment: payment) // Throws exception
XCTAssertTrue(model.payments?.contains(payment) ?? false)
}
In the test case above, the model variable is the view model class, which has a simple one line implementation...
func addPayment(payment: Payment) throws {
try self.budget?.addPayment(payment: payment)
}
This in turn calls through to the Budget class where the Realm interactions take place.
func addPayment(payment: Payment) throws {
let realm = try Realm()
try realm.write {
_payments.append(payment)
}
}
Note that contrary to the exception message the private var _payments = List<Payment>() property is being modified within a Realm write transaction.
I've configured the default Realm configuration in the unit test as follows...
override func setUp() {
var config = Realm.Configuration.init()
// Set this as the configuration used for the default Realm
Realm.Configuration.defaultConfiguration = config
config.inMemoryIdentifier = "BudgetTests"
try! repository = BudgetRepository.init(realm: Realm(configuration: config))
try! initialiseViewModel()
}
Updating the test setup to initialise the realm using the no-args initializer fixed the issue e.g.
Replacing...
try! repository = BudgetRepository.init(realm: Realm(configuration: config))
... with ...
try! repository = BudgetRepository.init(realm: Realm())
Oddly this would appear to imply that opening a Realm with the default configratuion e.g. Realm() does not produce the equivalent Realm as initializating one and manually supplying the default configuration.
I'm attempting to update an entity in CoreData. Here are some of my declarations.
static var appDelegate = UIApplication.shared.delegate as! AppDelegate
static var context = appDelegate.persistentContainer.viewContext
static var entity = NSEntityDescription.entity(forEntityName: "PData", in: context)
static var newPData = NSManagedObject(entity: entity!, insertInto: context)
I'm somewhat certain the fact that they're static isn't relevant.
PData is the name of the entity (short for persistent data).
Later on, I set the values I'd like to save using newPData.setValue("foo", forKey: "bar"), but when I actually attempt to save them with context.save(), I get NSCocoaErrorDomain Code 133020 "Could not merge changes."
I should mention that this is meant to occur directly after deleting an existing entry in PData (replacing an old entity instance with a new one).
I've done some reading, and I've found that the reason for this error is that the default way that Swift handles a CoreData merge conflict is to throw an error. I'd like to change my CoreData settings such that changes from memory overwrite changes already stored within the entity in CoreData, but I'm not sure as to how I'd going about doing this.
The Apple documentation shows lots of different merge policy options, but doesn't have an example showing how to implement them. I think the one I need to use is NSMergeByPropertyStoreTrumpMergePolicy, but I have no idea how to actually set said policy as the merge policy.
I found the answer - to set the context's merge policy, you simply do
context.mergePolicy = NSMergePolicy(merge: NSMergePolicyType.mergeByPropertyObjectTrumpMergePolicyType)
I was getting tripped up by trying to do
context.mergePolicy = mergeByPropertyObjectTrumpMergePolicyType)
but I guess it's necessary to spawn an NSMergePolicy object. I just assumed that the actual merge policy (mergeByPropertyObjectTrumpMergePolicyType) would be of the correct type by default.
You can put mergePolicy when initiate persistentContainer
var persistentContainer: NSPersistentContainer = {
let modelURL = Bundle.main.url(forResource: DB_NAME, withExtension: "momd")!
let container = NSPersistentContainer.init(name: DB_NAME, managedObjectModel: NSManagedObjectModel(contentsOf: modelURL)!)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
if let error = error as NSError? {
QiscusLogger.errorPrint("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
Merge policies are used in coredata to resolve the conflict issue between persistent store and different managed object context, it depends on you which merge policy is suitable for your application.
As from the code snippet it seams that you are using single managed object conext.
Please try below code-
appDelegate.persistentContainer.viewContext.automaticallyMergesChangesFromParent = true
appDelegate.persistentContainer.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyStoreTrump
In Swift 5 once you create a project with Core Data, then you have Persistence.swift file in your project directory.
In this class put mergePolicy code in the last of init() function.
init(inMemory: Bool = false) {
....
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
}
And it's work for me 😉.
This is my Realm object:
class AchievementDate : Object {
dynamic var date: Date = Date()
dynamic var apple: Int = Int(0)
func save() {
do {
let realm = try Realm()
try realm.write {
realm.add(self)
}
} catch let error as NSError {
fatalError(error.localizedDescription)
}
}
}
I change apple's value in View controller's viewDidLoad() method, as you can see:
override func viewDidLoad() {
super.viewDidLoad()
achievementDate.apple = 2
achievementDate.save()
}
then I update the apple's value when user clicks the pause button on the screen like this:
#IBAction func pausedButtonTapped(_ sender: UIButton) {
achievementDate.apple += 1
achievementDate.save()
}
Xcode runs it successfully but when I click the pause button, the app crashes. In the console it says:
*** Terminating app due to uncaught exception 'RLMException', reason: 'Attempting to modify object outside of a write transaction - call
beginWriteTransaction on an RLMRealm instance first.'
I am quite confused about this, btw, what does transaction mean in general? Thanks a lot.
A write transaction is used to group modifications of objects within a Realm into a single unit of work. Managed Realm objects may only be modified within a write transaction. The write transaction is scoped to the block you pass to the call to Realm.write(_:). The call to write begins a write transaction, the body is executed with the transaction active, and when the block returns the write transaction is committed and the changes are persisted to the Realm file.
You've not shared how achievementDate is initialized, but it seems safe to assume based on the exception you're seeing that it is an AchievementDate instance that is a managed object (that is, it was either created then added to a Realm, or it was loaded from a Realm). As the exception notes, you can only modify managed objects within a write transaction. You can either expand the scope of your write transaction to encompass the modification of the managed object, or you can avoid modifying a managed object altogether (by adding a primary key to your model class, and using either Realm.create(_:value:update:) or Realm.add(_:update:) with update: true to update an existing object with the given primary key value).
First, you need to define a Primary Key if you want to update the model:
class AchievementDate: Object {
dynamic var id = 0
dynamic var date:Date = Date()
dynamic var apple:Int = Int(0)
override static func primaryKey() -> String? {
return "id"
}
}
Then update the model like:
static func save(achievementDate:AchievementDate) {
let realm = try! Realm()
try! realm.write {
realm.add(achievementDate, update: true)
}
}
Alternatively, if you don't want primary Key, and the model is already retrieved from Realm, you could update the model like this:
let realm = try! Realm()
try! realm.write {
achievementDate.apple = 2
}
I'm trying to create a bundle realm for my application. I thought it should be quite simple. Since I already have all needed records in Parse, I just have to:
create realm models and objects
load parse records to realm objects
save the realm
So, I created two realm models:
class RealmContainer : Object {
dynamic var rContainerId: String! //should be equal objectId from Parse
dynamic var rContainerName: String! //should be equal "name" field from Parse
...
var messages = List<RealmMessage>()
override static func primaryKey() -> String? {
return "rContainerId"
}
}
and
class RealmMessage : Object {
dynamic var rMessageId: String!
...
dynamic var rParentContainer: RealmContainer!
}
Getting results from Parse seems to be working. Also my realm objects are also good
var allUserContainers: [RealmContainer] = []
I was able to populate this array with values from Parse. But when I'm trying to save this realm, I'm getting a) nothing or b) error message
My code (this one'll get nothing):
let realm = try! Realm()
try! realm.write {
realm.add(self.allUserContainers[0])
print(Realm().path)
print(realm.path)
}
My code (this one'll get nothing too):
let realm = try! Realm()
try! realm.write {
realm.create(RealmContainer.self, value: self.allUserContainers[0], update: true)
print(Realm().path)
print(realm.path)
}
My code 3 (this will get me an error message "Terminating app due to uncaught exception 'RLMException', reason: 'Illegal recursive call of +[RLMSchema sharedSchema]. Note: Properties of Swift Object classes must not be prepopulated with queried results from a Realm"):
//from firstViewController, realm is a global variable
let realm = try! Realm()
//another swift module
try! realm.write {
realm.create(RealmContainer.self, value: self.allUserContainers[0], update: true)
print(Realm().path)
print(realm.path)
}
Obviously I don't properly understand how it should work, but I tried several swift/realm tutorials and they were actually straightforward. So, what did I do wrong?
Update
So, I updated my code to make it as much simple/readable as possible. I have a Dog class, and I am trying to get Dogs from Parse and put them to Realm.
AppDelegate.swift
let realm = try! Realm() //global
Dog.swift
class Dog : Object {
dynamic var name = ""
dynamic var age = 0
}
User.swift (getDogs and putDogs functions)
class User {
var newDogs:[Dog] = []
...
func getDogs() {
self.newDogs = []
let dogsQuery = PFQuery(className: "Dogs")
dogsQuery.limit = 100
dogsQuery.findObjectsInBackgroundWithBlock { (currentModes, error) -> Void in
if error == nil {
let tempModes:[PFObject] = currentModes as [PFObject]!
for var i = 0; i < tempModes.count; i++ {
let temp = Dog()
temp.name = currentModes![i]["dogName"] as! String
self.newDogs.append(temp)
}
} else {
print("something happened")
}
}
}
...
func putDogs() {
print(self.newDogs.count)
try! realm.write({ () -> Void in
for var i = 0; i < newDogs.count; i++ {
realm.add(newDogs[i])
}
})
try! realm.commitWrite() //doesn't change anything
}
error message still the same:
Terminating app due to uncaught exception 'RLMException', reason:
'Illegal recursive call of +[RLMSchema sharedSchema]. Note: Properties
of Swift Object classes must not be prepopulated with queried
results from a Realm
I believe I just have some global misunderstanding about how Realm is working because it is extremely simple configuration.
About your RealmSwift code : You have implemented it right.
When you declare a class of realm in swift, it's a subclass of Object class. Similarly for the parse it's subclass of PFObject.
You custom class have to have only one base class. So can't use functionalities of both the libraries Parse as well as Realm.
If you have to have use both Parse and Realm, you need to declare two different classes like RealmMessage and ParseMessage.
Retrieve data for ParseMessage from parse and copy properties to RealmMessage.
Why you want to use both Parse and Realm ?
parse also provides local data store by just writing Parse.enableLocalDatastore() in appDelegate before
Parse.setApplicationId("key",
clientKey: "key")
Your Realm Swift code seems fine. Realm is very flexible when creating objects using (_:value:update:), in being able to take in any type of object that supports subscripts. So you could even directly insert PFObject if the schemas matched.
The problem seems to be how you're populating the allUserContainers array. As the error says, you cannot fetch a Realm Object from Realm and then try and re-add it that way. If you're trying to update an object already in Realm, as long as the primary key properties match, you don't need to supply the whole object back again.
Can you please revisit the logic of how your allUserContainers variable is being populated, and if you can't fix it, post the code into your question?
Sidenote: You probably don't need to define your Realm properties as implicitly unwrapped as well. This is the recommended pattern:
class RealmContainer : Object {
dynamic var rContainerId = ""
dynamic var rContainerName = ""
}
Actually I found what was wrong: as i suspected it was a stupid mistake, some property in my class was UIImage type, and Realm just can't work with this type (even if I'm not trying to put objects of this class into realm). So, my bad. I am slightly embarassed by it, but will not delete my question because error messages from Realm were not very understandable in this case
I am trying to use RestKit with Realm.io and am having an issue with property data types. I have a property that is an integer. RestKit seems to only want to map to a NSNumber type (https://github.com/RestKit/RestKit/wiki/Object-mapping), while a realm object only allows properties that are primitives (Int, Float, Double, etc (http://realm.io/docs/cocoa/0.87.4/#property-types)).
I've seen other indications that these two frameworks can be used together (Can i use RestKit and Realm.io), but I'm not sure how to get around this issue.
For example, if I use an NSNumber data type, the RLMObject will fail and give the error
'RLMException', reason: ''NSNumber' is not supported as an RLMObject property...'
But if I use a primitive data type to make realm happy I get a 'key value coding-compliant' error from RestKit.
It's the worst.
I've tried adding a RestKit value transformer to the property, thinking that maybe I would be able to toggle back and forth between data types whenever a rest call needed to be made. But that strategy is on it's way to being a stackoverflow question of its own.
It's actually the worst.
Has anybody done this? Am I onto something with the value transformer? Thanks in advance!
I'm not having any issues getting RestKit to work with primitive number types in Swift. Here's my code:
class TwitterError: RLMObject {
dynamic var message = ""
dynamic var code = 0
override var description: String { return "Error \(code): \(message)" }
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let mapping = RKObjectMapping(forClass: TwitterError.self)
mapping.addAttributeMappingsFromDictionary(["message": "message", "code": "code"])
let responseDescriptor = RKResponseDescriptor(mapping: mapping, pathPattern: nil, keyPath: "errors", statusCodes: nil)
let url = NSURL(string: "http://api.twitter.com/1/statuses/public_timeline.json")
let request = NSURLRequest(URL: url!)
let operation = RKObjectRequestOperation(request: request, responseDescriptors: [responseDescriptor])
operation.setCompletionBlockWithSuccess(nil, failure: nil)
operation.start()
}
}
which outputs a description of "Error 92: SSL is required", which means that both code (Int) and message (String) are being parsed properly.