I am trying to save a simple object with Realm but the app keeps crashing when trying to make a write transaction even when it's wrapped in a Do Catch block.
let theme = Theme()
theme.name = "Custom Theme"
theme.backgroundColor = backgroundColor
theme.accentColor = accentColor
theme.numberColor = numColor
theme.functionColor = funcColor
// Add to the Realm inside a transaction
do {
try Realm().write {
do {
try Realm().add(theme, update: true)
} catch {
print("Error saving data")
}
}
} catch {
print("Realm.write error")
}
Here is the object 'Theme'
class Theme : Object {
dynamic var name = ""
dynamic var backgroundColor = ""
dynamic var accentColor = ""
dynamic var numberColor = ""
dynamic var functionColor = ""
override static func primaryKey() -> String? {
return "name"
}
}
Here is a screenshot of the crash
SIGABRT Crash
EDIT: The code above that causes the crash is only executed when a button is clicked. There is no console output either. I am bringing realm in via CocoaPods.
Ah, it might have something to do with the way you're creating the realm instances, try this:
let realm = try! Realm()
do {
try realm.write {
do {
try realm.add(theme, update: true)
} catch {
print("Error saving data")
}
}
} catch {
print("Realm.write error")
}
Though, usually you won't need to wrap your transactions into a do-catch block:
let realm = try! Realm()
try! realm.write {
realm.add(theme, update: true)
}
Related
I have added realm integration in my app. Process is that,
1] if list is not empty then reload the tableview , and at the same time call an api, receive its response..
2] check value present against Id or not, if present delete that value from realm, again add value to realm and reload the tableview.. Code is working fine, if i wait for the 2nd step completion. But 2nd step is totally asynchronous..Here is what I have tried
And crash is happening when i change viewcontroller before the completion 2nd step.
public class Features : Object , Codable{
#objc dynamic var intellinectsId : String?
#objc dynamic var serviceName : String?
#objc dynamic var android_icon : String?
#objc dynamic var parentUrl : String?
#objc dynamic var url : String?
#objc dynamic var mobileOrder : String?
enum CodingKeys: String, CodingKey {
case serviceName = "serviceName"
case android_icon = "android_icon"
case parentUrl = "parentUrl"
case url = "url"
case mobileOrder = "mobileOrder"
case intellinectsId = "intellinectsId"
}
required convenience public init(from decoder: Decoder) throws {
self.init()
let values = try decoder.container(keyedBy: CodingKeys.self)
serviceName = try values.decodeIfPresent(String.self, forKey: .serviceName)
android_icon = try values.decodeIfPresent(String.self, forKey: .android_icon)
parentUrl = try values.decodeIfPresent(String.self, forKey: .parentUrl)
url = try values.decodeIfPresent(String.self, forKey: .url)
mobileOrder = try values.decodeIfPresent(String.self, forKey: .mobileOrder)
intellinectsId = try values.decodeIfPresent(String.self, forKey: .intellinectsId)
}
required init() {
}
}
I have created single class for RealmService
class RealmService {
private init() {}
/// To get singleton class
static let shared = RealmService()
/// To get realm object instance
var realm = try! Realm()
/// To create a record or adding new object to database
func create<T: Object>(_ object: T) {
do {
try realm.safeWrite {
//realm.add(object)
realm.create(T.self,value: object)
//realm.create(T.self, value: object, update: .modified)
}
} catch {
post(error)
}
}
/// To update/modify particular record/object in the database
func update<T: Object>(_ object: T, with dictionary: [String: Any?]) {
do {
try realm.write {
for (key, value) in dictionary {
object.setValue(value, forKey: key)
}
realm.add(object, update: .all) //add(object, update: true)
}
} catch {
post(error)
}
}
/// To delete/remove record or object database
func delete<T: Object>(_ object: T) {
do {
try realm.write {
realm.delete(object)
}
} catch {
post(error)
}
}
/// To handle the errors while performing realmDB opration
func post(_ error: Error) {
NotificationCenter.default.post(name: NSNotification.Name("RealmError"), object: error)
}
/// To observe realm error in the particular View controller
func observeErrors(in vc: UIViewController, completionHandler: #escaping(Error?) -> Void) {
NotificationCenter.default.addObserver(forName: NSNotification.Name("RealmError"), object: nil, queue: nil) { (notification) in
completionHandler(notification.object as? Error)
}
}
/// To stop observing errors in particular View Controller
func stopObservingErrors(in vc: UIViewController) {
NotificationCenter.default.removeObserver(vc, name: NSNotification.Name("RealmError"), object: nil)
}
/// To delete complete realm form the app
func completeDeleteRealm() {
let realmURL = Realm.Configuration.defaultConfiguration.fileURL!
let realmURLs = [
realmURL,
realmURL.appendingPathExtension("lock"),
realmURL.appendingPathExtension("note"),
realmURL.appendingPathExtension("management")
]
for URL in realmURLs {
do {
try FileManager.default.removeItem(at: URL)
} catch {
post(error)
}
}
}
}
Now in View controller , I am taking a value from realm, against id, like this
var dashboardList : Results<Features>{
get{
return RealmService.shared.realm.objects(Features.self).filter(NSPredicate(format: "intellinectsId == %#", HelperFunctions().getUserInfoFromDefaults()?.intellinectsId ?? ""))
}
}
.. as given in step 1st, if dashboardList count is > 0 then reload the tableview , simultaneously , call and api to fetch the details and again I have performed 2nd step like below
if let responseList = response.array, responseList.count > 0{
//remove existing data from realm
let removeHMC = RealmService.shared.realm.objects(Features.self).filter(NSPredicate(format: "intellinectsId == %#",intellinectsId))
if let realm = try? Realm(){
try? realm.write {
realm.delete(removeHMC)
}
}
if let featuresList = responseList[0]["features"].array, featuresList.count > 0{
for val in featuresList{
let feature = Features()
feature.intellinectsId = intellinectsId
feature.serviceName = val["serviceName"].string
feature.android_icon = val["android_icon"].string
feature.parentUrl = val["parentUrl"].string
feature.url = val["url"].string
feature.mobileOrder = val["mobileOrder"].string
RealmService.shared.create(feature)
}
}
}
And if i wait for this completion then it works fine. but if i go on next vc. I am getting an error like
libc++abi.dylib: terminating with uncaught exception of type NSException
after looking at the specific issue, I received an error
Terminating app due to uncaught exception 'RLMException', reason: 'Object has been deleted or invalidated.'
I applied this trick also.. But after deleting value from realm , showing again to the tableview causes this issue. I am unable to figure out. Kindly help
In your response when you are deleting the previous features list you are creating a new instance of Realm() which will cause inconsistencies between those two.
Try removing that and using the same RealmService.shared like this
//remove existing data from realm
let removeHMC = RealmService.shared.realm.objects(Features.self).filter(NSPredicate(format: "intellinectsId == %#",intellinectsId))
RealmService.shared.delete(removeHMC)
I am fetching data from FireBase and save it inside my realm but it is not working as expected :
for doc in docs {
let shopData = doc.data()
let newShop = RLMShop()
newShop.shopName = shopData["name"] as? String ?? "Empty Name"
self.saveShop(shop: newShop) // My issue is here
}
My saveShop function :
func saveShop(shop: RLMShop) {
do {
try realm.write {
realm.add(shop)
}
} catch {
print("Error saving shop \(error)")
}
}
Calling save function is not saving my object.
The problem you have is that you are creating a RLMShop object but it is not linked to a RLMShopsCategory object, therefore your shopsList will not contain the new object.
// Fetch the RLMShopsCategory that you wish to add the RLMShop too
// Using Primary Key here just as an example
let shopsCategory = realm.object(ofType: RLMShopsCategory.self, forPrimaryKey: "YourKey")
for doc in docs {
let shopData = doc.data()
let newShop = RLMShop()
newShop.shopName = // setting the properties etc
// This is the part you are missing
// You need to append the newShop to your shopsCategory object
try! realm.write {
shopsCategory.shopsList.append(newShop)
}
}
I have products in my Realm database like this
I want to update my realm database based on productID, so I don't need to add another product over and over again. let say I want to update quantity of product that has productID = "a" to be 5.
I have tried to write something like this.
let selectedProductID = "a"
let productsInRealmDatabase = realm.objects(Product.self)
let productIndex = productsInRealmDatabase.index(where: {$0.productID == selectedProductID})
if let productIndex = productIndex {
do {
try realm.write {
var productRealm = productsInRealmDatabase[productIndex]
productRealm.quantity = 5
productsInRealmDatabase[productIndex] = productRealm // ERROR HERE
}
} catch {
// error Handling
}
}
but I got error in : productsInRealmDatabase[productIndex] = productRealm
Error Message: Cannot assign through subscript: subscript is get-only
so how to update realm object based on the certain property in Realm?
You should use Realm's own filter method which accepts an NSPredicate and returns an auto-updating Results instance rather than Swift's filter when operating on Realm collections. Than either update the properties of the fetched prouduct or create a new one and save that to Realm.
let selectedProductID = "a"
let productsInRealmDatabase = realm.objects(Product.self)
let matchingProduct = productsInRealmDatabase.filter("productID == %#", selectedProductID).first
if let matchingProduct = matchingProduct {
do {
try realm.write {
matchingProduct.quantity = 5
}
} catch {
// error Handling
}
} else {
let newProduct = Product()
newProduct.productID = selectedProductID
newProduct.quantity = 5
do {
try realm.write {
realm.add(newProduct)
}
} catch {
// error Handling
}
}
If you want your Products to be unique based on their productID property, you use also set productID as the primaryKey of your Object subclass.
class Product:Object {
#objc dynamic var productID = ""
...
override static func primaryKey() -> String? {
return "productID"
}
}
Try this -
let selectedProductID = "a"
let productsInRealmDatabase = realm.objects(Product.self)
let filteredProducts = productsInRealmDatabase.filter("productID = \(selectedProductID)")
do {
try realm.write {
filteredProducts.forEach { product in
product.quantity = 5
}
}
} catch {
// error Handling
}
While inserting data to your database inside the insert function mark update key as true and then try updating the value. eg:
static func insertData() {
//Your insertion code//
try! realm.write {
realm.add(request, update: true)
}
}
static func updateData(productId: String, quantity: Int) {
let product = self.getProductData(prodId: productId)
let realm = try! Realm()
try! realm.write {
product?.quantity = quantity
}
}
Hope this helps you out.
I use Xcode 9.2 with swift 4 for an IOS application
everything work nice , but when i add realm [ save to DB after click on the button ] , and then click into the button the app crush with this message
SIGNAL SIGABRT
and when i changed the realm method with just a simple methode with
print("clicked")
it works
i use this pod line to add the library
pod 'RealmSwift'
and i have the code below
floaty.addItem("Share", icon: UIImage(named: "share-variant")! , handler:{ item in
self.shareTxt()
})
floaty.addItem("Save", icon: UIImage(named: "content-save")! , handler:{ item in
self.saveToDB()
})
func saveToDB() {
let realm = try! Realm()
realm.beginWrite()
realm.create(News.self, value: [SingleItem?.id])
try! realm.commitWrite()
}
class News: Object {
#objc dynamic var new_id = ""
}
First, make the property new_id as the Primary key:
class News: Object {
#objc dynamic var new_id = ""
override static func primaryKey() -> String? {
return "new_id"
}
}
Then, add or update the model:
func saveToDB() {
if let newId = SingleItem?.id {
let realm = try! Realm()
let news = realm.objects(News.self).filter("new_id == \(newId)").first
if news != nil {
try! realm.write {
//optional, updating some fields...
//like: news.title = "some title"
}
}
else{
let news = News(value: ["new_id", newId])
try! realm.write {
realm.add(news)
}
}
}
}
I've tested my code with 3 x iPhone 5's, a 5s, 6, 6s, and 7.
I'm getting the above error on all of the iPhone 5 devices only. No idea what is going on here but perhaps the fact that the 5's are 32bit devices may be a clue?
I'm calling the following method from a viewcontroller class
func startRecording() {
disableControls()
CoreDataStack.shared.performForegroundTask { (context) in
let sessionInfo = SessionInfo(context: context)
sessionInfo.startTime = Date().timeIntervalSince1970
sessionInfo.userId = self.config.userId
sessionInfo.devicePosition = self.config.devicePosition.rawValue
sessionInfo.deviceType = self.config.deviceType.rawValue
sessionInfo.deviceNumber = self.config.deviceNumber
sessionInfo.deviceSide = self.config.deviceSide.rawValue
do {
try context.obtainPermanentIDs(for: [sessionInfo])
} catch {
print("Error obtaining permanent ID for session info record")
return
}
CoreDataStack.shared.saveViewContextAndWait()
DispatchQueue.main.async {
guard sessionInfo.objectID.isTemporaryID == false else {
print("ObjectID is temporary")
return
}
self.recording = true
self.statusLabel.text = "Recording..."
self.recordManager.start(sessionUID: sessionInfo.uid)
}
}
}
The config variable is a simple struct:
struct Configuration {
var userId: String = "Unknown"
var deviceType: DeviceType = .phone // enum: String
var deviceSide: DeviceSide = .notApplicable // enum: String
var deviceNumber: Int16 = 1
var devicePosition: DevicePosition = .waist // enum: String
}
The CoreDataStack is here:
final class CoreDataStack {
static let shared = CoreDataStack()
private init() {}
var errorHandler: (Error) -> Void = { error in
log.error("\(error), \(error._userInfo)")
}
private struct constants {
static let persistentStoreName = "Model"
}
private lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: constants.persistentStoreName)
container.loadPersistentStores(completionHandler: { [weak self] (storeDescription, error) in
if let error = error {
self?.errorHandler(error)
}
})
return container
}()
lazy var viewContext: NSManagedObjectContext = {
self.persistentContainer.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
self.persistentContainer.viewContext.automaticallyMergesChangesFromParent = true
try! self.persistentContainer.viewContext.setQueryGenerationFrom(.current)
return self.persistentContainer.viewContext
}()
private lazy var backgroundContext: NSManagedObjectContext = {
let context = self.persistentContainer.newBackgroundContext()
context.mergePolicy = NSMergePolicy.mergeByPropertyStoreTrump
return context
}()
func performForegroundTask(_ block: #escaping (NSManagedObjectContext) -> Void) {
self.viewContext.performAndWait {
block(self.viewContext)
}
}
func performBackgroundTask(_ block: #escaping (NSManagedObjectContext) -> Void) {
backgroundContext.perform {
block(self.backgroundContext)
}
}
func saveBackgroundContext() {
viewContext.performAndWait {
do {
if self.viewContext.hasChanges {
try self.viewContext.save()
}
} catch {
self.errorHandler(error)
}
self.backgroundContext.perform {
do {
if self.backgroundContext.hasChanges {
try self.backgroundContext.save()
self.backgroundContext.refreshAllObjects()
}
} catch {
self.errorHandler(error)
}
}
}
}
func saveViewContext() {
viewContext.perform {
if self.viewContext.hasChanges {
do {
try self.viewContext.save()
} catch {
self.errorHandler(error)
}
}
}
}
func saveViewContextAndWait() {
viewContext.performAndWait {
if self.viewContext.hasChanges {
do {
try self.viewContext.save()
} catch {
self.errorHandler(error)
}
}
}
}
}
The code is bombing out on the following line in the startRecording method:
try context.obtainPermanentIDs(for: [sessionInfo])
Edit:
I've created a stripped down test application consisting of only the CoreDataStack and a model with one entity with one attribute of type string. I'm still getting the same error on 3x iPhone 5's only. 5s, 6, 6s, 7 all work fine.
It would imply the problem lies with the CoreDataStack perhaps?
Github repo here
A couple of people have asked if I solved this, so here is what I did. This is not really a solution but more of a workaround. It may not be appropriate for everyone but worked for me.
All I did was remove the line
try! self.persistentContainer.viewContext.setQueryGenerationFrom(.current)
from the CoreDataStack and the issue went away...
I'll take a couple of guesses based on a short look at your code.
The old-school developer in me is drawn to the Configuration member var deviceNumber: Int16 = 1. Incorrect alignment settings or old compilers might cause the next item to have the wrong alignment. You could try making it the last item in the struct.
Another item which stands out is the assignment sessionInfo.deviceNumber = self.config.deviceNumber. This looks to be assigning an Int16 to an NSNumber which may be a problem. (I am assuming that SessionInfo is an NSManagedObject based on the overall code and its initializer taking a context argument. That would mean all numeric members are NSNumbers.)
Try changing the line to
sessionInfo.deviceNumber = NSNumber(int:self.config.deviceNumber)
I'm not yet familiar with the iOS 10 additions to Core Data, but from what I'm reading the viewContext is read-only. But viewContext is being used to create a new object in this code. Is the Xcode console showing any more information when you get to this point in the debugger?
NSPersistentContainer is a setup for a core data stack with clear expectation on how to use it. You are misusing it. viewContext is readonly so remove performForegroundTask, saveViewContext and saveViewContextAndWait. Don't change the viewContext's mergePolicy. In performBackgroundTask just use NSPersistentContainer's performBackgroundTask which has the same method signature. Don't use newBackgroundContext. If you are using NSPersistentContainer your CoreDataStack should be doing next to nothing.
If you want a different custom stack unlike what NSPersistentContainer sets up then don't use NSPersistentContainer - just create your own stack. But the setup that you are trying to write has major problems. Writing from both background contexts and the viewContext has major problems when writes happen at the same time. mergePolicy can help that but you can end up missing information that you thought you saved. You are much better off learning to use the stack that NSPersistentContainer sets up.