RealmSwift initializers - Xcode fix-it keeps getting it wrong - ios

I cannot get Realm working when I want to provide initializer for a class, Xcode endlessly suggest errors.
I decided to upload two screenshots instead of code snippet to make it easier to see errors
I follow the suggestions and end up with this
The last error tells "Use of undeclared type 'RLMObjectSchema'
I use the latest 0.99 version of RealmSwift

The recommended way is creating memberwise convenience initializer, like the following:
class Item: Object {
dynamic var isBook: Bool = true
dynamic var numberOfPages: Double = 0
dynamic var isInForeignLanguage: Bool = true
dynamic var isFictional: Bool = true
dynamic var value: Int {
get {
return calculalatedValue()
}
}
convenience init(isBook: Bool, numberOfPages: Double, isInForeignLanguage: Bool, isFictional: Bool) {
self.init()
self.isBook = isBook
self.numberOfPages = numberOfPages
self.isInForeignLanguage = isInForeignLanguage
self.isFictional = isFictional
}
...
}
You cannot omit default initializer because Realm needs default initializer for instantiating objects for querying. When querying to the Realm, Realm calls default initializer internally to instantiate the objects.
You can also override default initializer, but we don't recommend it. Because when you override the default initializer, you should override other required initializers inherited from ObjC layer due to Swift type system limitation. Also you should import both Realm and RealmSwift frameworks. Because there are Objective-C only class in the parameters of those initializers.
import RealmSwift
import Realm // Need to add import if you override default initializer!
class Item: Object {
dynamic var isBook: Bool = true
dynamic var numberOfPages: Double = 0
dynamic var isInForeignLanguage: Bool = true
dynamic var isFictional: Bool = true
dynamic var value: Int {
get {
return calculalatedValue()
}
}
required init() {
super.init()
}
required init(realm: RLMRealm, schema: RLMObjectSchema) {
super.init(realm: realm, schema: schema)
}
required init(value: AnyObject, schema: RLMSchema) {
super.init(value: value, schema: schema)
}

Try:
required convenience init(...) {
self.init()
...
}
See https://github.com/realm/realm-cocoa/issues/1849

Related

Error with retrieve / save custom object in core data

I have developed an ios application that allow users to edit a musical score sheet and now i'd like to implement data persistence to prevent the discarding of changes.
Reading on ios documentation i've noticed that exists different ways to improve data persistence and I believe that the best way for my application is Core Data.
Considering that my application use a lot of custom object i met a lot of problems.
I'm trying to use core data to save an entity, referred to a score sheet, composed by two attributes:
name: String
score: Array of another custom object (Measure), composed by other custom object (Score Element)
According to documentation and other q/a I've decided to use a Trasformable type on the model:
So I've declared a generic class used as trasformer for score attribute:
public class NSSecureCodingValueTransformer<T: NSSecureCoding & NSObject>: ValueTransformer {
public override class func transformedValueClass() -> AnyClass { T.self }
public override class func allowsReverseTransformation() -> Bool { true }
public override func transformedValue(_ value: Any?) -> Any? {
guard let value = value as? T else { return nil }
return try? NSKeyedArchiver.archivedData(withRootObject: value, requiringSecureCoding: true)
}
public override func reverseTransformedValue(_ value: Any?) -> Any? {
guard let data = value as? NSData else { return nil }
let result = try? NSKeyedUnarchiver.unarchivedObject(
ofClass: T.self,
from: data as Data
)
return result
}
/// The name of this transformer. This is the name used to register the transformer using `ValueTransformer.setValueTransformer(_:forName:)`
public static var transformerName: NSValueTransformerName {
let className = NSStringFromClass(T.self)
return NSValueTransformerName("DHC\(className)ValueTransformer")
}
/// Registers the transformer by calling `ValueTransformer.setValueTransformer(_:forName:)`.
public static func registerTransformer() {
let transformer = NSSecureCodingValueTransformer<T>()
ValueTransformer.setValueTransformer(transformer, forName: transformerName)
}
}
Using in this way a DHCMeasureValueTransformer as trasformer in DataModel file.
The problem is that when i save, no error occurs but when i fetch data for a new restart of application, i can fetch just the name of score sheet, while the score array it's empty, like if no elements it's been put inside (clearly, before of save, i've try to print array content, that prove that i'm working with a non empty array)
Here is the code of the save:
static func saveContext() {
let context = getContext()
do {
try context.save()
} catch {
print("error during the save.")
}
}
And here is the code of two classes of the entity object:
// DataClass
#objc(ScoreSheet)
public class ScoreSheet: NSManagedObject {
static var supportsSecureCoding: Bool {
return true
}
}
//DataProperties
extension ScoreSheet {
#nonobjc public class func fetchRequest() -> NSFetchRequest<ScoreSheet> {
return NSFetchRequest<ScoreSheet>(entityName: "ScoreSheet")
}
#NSManaged public var name: String
#NSManaged public var score: [Measure]
}
Clearly Measure class implements NSSecureCoding and method for decode and encode the object.
Here is Measure class implementation:
import Foundation
class Measure: NSObject, NSCoding, NSSecureCoding {
var elements : [ScoreElement] = []
var timeSig : TimeSignature
var clef : Clef
static var supportsSecureCoding = true
init(time : TimeSignature, clef : Clef) {
self.timeSig = time
self.clef = clef
}
func encode(with encoder: NSCoder) {
encoder.encode(self.elements, forKey: "elements")
encoder.encode(self.timeSig, forKey: "timeSig")
encoder.encode(self.clef, forKey: "clef")
}
required convenience init? (coder decoder: NSCoder) {
let elements = decoder.decodeObject(forKey: "elements") as! [ScoreElement]
let timeSig = decoder.decodeObject(forKey: "timeSig") as! TimeSignature
let clef = decoder.decodeObject(forKey: "clef") as! Clef
self.init(time: timeSig, clef: clef)
self.elements = elements
}
}
I'm not sure what's going wrong but there are a couple of things that need fixing that might affect your results.
Firstly, the computed transformer name is not the same as the one you're trying to use. When this line executes, and T is Measure,
let className = NSStringFromClass(T.self)
Then className is going to be something like MyProjectName.Measure. The computed transformer name ends up as something like NSValueTransformerName(_rawValue: DHCMyProjectName.MeasureValueTransformer), which doesn't match what you're using in the data model. All of which means that your transformer isn't getting used.
But that probably doesn't matter because if Measure conforms to NSSecureCoding and all of Measure's properties (ScoreElement, TimeSignature, Clef) also conform to NSSecureCoding (which seems to be the case since your code isn't throwing exceptions), then you don't need a custom transformer at all. If a transformable property type conforms to NSSecureCoding then Core Data will automatically use NSSecureCoding. You don't need a custom transformer unless you don't want to or can't conform to NSSecureCoding for some reason. Because of this, it doesn't matter that your transformer isn't being used.
As for why Measure isn't surviving the encode/decode process, I don't know, but you may help clear things up by removing the distraction of the unnecessary encode/decode class. I'd also suggest putting a breakpoint in Measure in the encode(with:) and init(coder:) methods. You should hit those breakpoints when saving and fetching data.

Why does adding a convenience init to a Realm object declaration mess with private values?

I have created a Realm object that needs to store an enum value. To do that I use a method outlined in this question which involves declaring a private property of type String, and then declaring another property of type Enum that sets/reads the private property using getters and setters.
For ease of reference here is the code for that:
#objcMembers
class PlaylistRealmObject: Object {
dynamic var id: String = UUID().uuidString
dynamic var created: Date = Date()
dynamic var title: String = ""
private dynamic var revisionTypeRaw: String = RevisionType.noReminder.rawValue
var revisionType: RevisionType {
get { return RevisionType(rawValue: revisionTypeRaw)! }
set { revisionTypeRaw = newValue.rawValue }
}
let reminders = List<ReminderRealmObject>()
let cardsInPlaylist = List<CardRealmObject>()
override static func primaryKey() -> String? {
return "id"
}
}
I have noticed though that if I add a convenience init to the class declaration (to make it a bit easier to initialise the object) the revisionType properties on the objects I end up with adopt the default value declared in the class, and NOT the revision type value passed to the class using the convenience init.
Here is the class declaration with a convenience init
#objcMembers
class PlaylistRealmObject: Object {
dynamic var id: String = UUID().uuidString
dynamic var created: Date = Date()
dynamic var title: String = ""
private dynamic var revisionTypeRaw: String = RevisionType.noReminder.rawValue
var revisionType: RevisionType {
get { return RevisionType(rawValue: revisionTypeRaw)! }
set { revisionTypeRaw = newValue.rawValue }
}
let reminders = List<ReminderRealmObject>()
let cardsInPlaylist = List<CardRealmObject>()
convenience init(title: String, revisionType: RevisionType) {
self.init()
self.title = title
self.revisionType = revisionType
}
override static func primaryKey() -> String? {
return "id"
}
}
And - to make things even more perplexing - if I simply remove the word 'private' from the revisionTypeRaw property, everything works fine!
I am confused. 1) Why does adding a convenience init have this effect? 2) Why does making the property 'public' resolve the issue?
I have created a demo Xcode project to illustrate the issue and can share it if anyone needs it.
Update:
I found the problem. It has nothing to do with the convenience init. I am using #objcMembers at the top of the class as per the Realm docs: https://realm.io/docs/swift/latest/#property-attributes
If you remove this and place #objc in front of the private keyword, everything works as would be expected. I guess the question then is: what explains this behaviour?
This is a good question but I think the issue is elsewhere in the code. Let's test it.
I created a TestClass that has a Realm managed publicly visible var, name, as well as a non-managed public var visibleVar which is backed by a Realm managed private var, privateVar. I also included a convenience init per the question. The important part is the privateVar is being set to the string "placeholder" so we need to see if that is overwritten.
class TestClass: Object {
#objc dynamic var name = ""
#objc private dynamic var privateVar = "placeholder"
var visibleVar: String {
get {
return self.privateVar
}
set {
self.privateVar = newValue
}
}
convenience init(aName: String, aString: String) {
self.init()
self.name = aName
self.visibleVar = aString
}
}
We then create two instances and save them in Realm
let a = TestClass(aName: "some name", aString: "some string")
let b = TestClass(aName: "another name", aString: "another string")
realm.add(a)
realm.add(b)
Then a button action to load the two objects from Realm and print them.
let testResults = realm.objects(TestClass.self)
for test in testResults {
print(test.name, test.visibleVar)
}
and the output:
some name some string
another name another string
So in this case, the default value of "placeholder" is being overwritten correctly when the instances are being created.
Edit:
A bit more info.
By defining your entire class with #objMembers, it exposes your propertites to Objective-C, but then private hides them again. So that property is not exposed to ObjC. To reverse that hiding, you have to say #objc explicitly. So, better practice is to define the managed Realm properties per line with #objc dynamic.

Realm Swift - save a reference to another object

I thought this would be pretty straightforward after reading here and here but I'm a bit stuck.
I have a 'favouriteWorkout' object that looks like this :
class FavouriteObject: Object {
#objc dynamic var favouriteWorkoutName = ""
#objc dynamic var workoutReference = WorkoutSessionObject()
override class func primaryKey() -> String? {
return "favouriteWorkoutName"
}
}
What I'm trying to do here is reference a WorkoutSessionObject in Realm that links from a WorkoutName when a workout is saved as a favourite.
My WorkoutSessionObject has a primary key of workoutID which is a UUID string. It looks like this :
class WorkoutSessionObject: Object {
#objc dynamic var workoutID = UUID().uuidString
#objc dynamic var workoutType = ""
let exercises = List<WorkoutExercise>()
#objc dynamic var totalExerciseCount = 0
#objc dynamic var rounds = 0
#objc dynamic var favourite : Bool = false
override class func primaryKey() -> String? {
return "workoutID"
}
}
I've then tried to save using this :
let favouriteWorkout = FavouriteObject()
favouriteWorkout.favouriteWorkoutName = favouriteName
favouriteWorkout.workoutReference = (realm.object(ofType: WorkoutSessionObject.self, forPrimaryKey: self.workoutID))!
do {
try realm.write {
realm.add(favouriteWorkout)
}
} catch {
print ("Error adding favourite")
}
but i get a crash when I run of :
'RLMException', reason: 'The FavouriteObject.workoutReference property must be marked as being optional.
However, when I then try to make it optional (by adding ?) it says
"Cannot use optional chaining on non-optional value of type 'WorkoutSessionObject"!
Summary
I want to save a reference of the workoutID of a WorkoutSessionObject in my FavouriteObject which is an actual link to the WorkoutSessionObject (so the properties can be accessed from favourites)
Update
using the answers below I've now sorted the problem of the workout reference. This is now showing in Realm as the proper format () under "workoutReference". However, I'm now getting "nil" in "workoutReference" when trying to save. I know the workoutID is coming through correctly as I am printing it in the console.
You need to change the declaration of workoutReference. First of all, you need to make it Optional by writing ? after the type. Secondly, you shouldn't assign a default value to it, it needs to be Optional for a reason. The linked docs clearly state that
to-one relationships must be optional
, and workoutReference is clearly a to-one relationship.
class FavouriteObject: Object {
#objc dynamic var favouriteWorkoutName = ""
#objc dynamic var workoutReference:WorkoutSessionObject?
override class func primaryKey() -> String? {
return "favouriteWorkoutName"
}
}
In property-cheatsheet you can see that a non-optional Object-property is not allowed, so you have to change it like the following:
class FavouriteObject: Object {
#objc dynamic var favouriteWorkoutName = ""
// here you have to make the property optional
#objc dynamic var workoutReference: WorkoutSessionObject?
override class func primaryKey() -> String? {
return "favouriteWorkoutName"
}
}

Swift mutating func in Singleton

In Swift I have this Singleton
struct Networking {
static let shared = Networking()
private var observed: Set<String> = []
}
I have to manipulate observed and I need to create useful method to insert and remove member in Set.
mutating func addObserver(for member: String) {
//other code
observed.insert(member)
}
mutating func removeObserver(for member: String) {
//other code
observed.remove(member)
}
The problem is when I try to call this methods like this
Networking.shared.addObserver(for: "x")
because I'm getting this error
cannot use mutating on immutable value: “shared” is a “let” constant
This error is pretty clear. shared is let and obviously it cannot be modified. But to modify the var I need to declare method as mutating. It's a vicious circle.
Any ideas?
Thanks.
If you want your Networking object to act as a singleton, why not make it a class instead of a struct?
class Networking {
static let shared = Networking()
private var observed: Set<String> = []
func addObserver(for member: String) {
//other code
observed.insert(member)
}
func removeObserver(for member: String) {
//other code
observed.remove(member)
}
}
Networking.shared.addObserver(for: "x")
This simplifies the code and solves your issue.
Basically your syntax is wrong, Networking() creates a new instance of the class.
To use the struct as singleton you have to write
Networking.shared.addObserver(for: "x")
Then declare shared as mutable
static var shared = Networking()
There is also another way of doing it
class Networking {
static let shared = Networking()
var observed: Set<String> = [] {
didSet {
print("set has changed")
}
}
}
Value Type
Since Set is a struct (a value type), the didSet block will be executed every time you add or remove and element to Set.
Networking.shared.observed.insert("a")
set has changed
Networking.shared.observed.insert("b")
set has changed
Networking.shared.observed.remove("a")
set has changed

Using Realm Swift with initializers throws constant errors

Xcode 7.1 and Swift 2.1 with latest Realm Swift 0.96.2
I created a model class for Realm but it is constantly throwing errors about inits. I understand initializers to an extent for subclasses but I can't wrap my head around this and why it fails. Here's the class I made:
import UIKit
import RealmSwift
class Boxes: Object {
dynamic var precessor: String = "B";
dynamic var id: Int = 0;
dynamic var boxNumber: String {
return "\(precessor) \(id)"; //computed property
}
dynamic var boxDescription: String? = "";
dynamic var brand: String? = "";
dynamic let dateCreated: NSDate
dynamic var dateUpdated: NSDate?
dynamic var photo: UIImage?
dynamic var tags: NSArray? = [];
override static func primaryKey() -> String? {
return "id"; //sets primary key of the model
}
init(precessor: String, id: Int, description: String, brand: String, dateCreated: NSDate, dateUpdated: NSDate) {
self.precessor = precessor;
self.boxDescription = description;
self.brand = brand;
self.dateUpdated = dateUpdated;
self.dateCreated = dateCreated;
super.init();
}
}
This won't build when I try and it tells me:
'required' initializer 'init()' must be provided by subclass of 'Object'
And that I need this line added:
required init() {
fatalError("init() has not been implemented")
}
That satiates the compiler enough to let me build the project. However when I run the project, it always errors out and give me the fatalError line in the output. I know it's doing this as a last resort initializer but I can't figure out why.
Is this related to a super initializer I'm missing somewhere? I'm relatively new to swift but I can get my initializers to work if I don't subclass my class with Object
You are required to implement init() but Xcode doesn't know how to implement if for you so it puts in fatalError("init() has not been implemented") to remind you to implement it.
You probably just want to call super. So:
required init() {
super.init()
}

Resources