Working with a legacy project that still has a big mix of ObjC and Swift. Using RealmSwift, how should you access your Realm instance from ObjC? Should you create a separate RLMRealm instance with an identical configuration? Is there a bridge between a Swift Realm struct and an ObjC RLMRealm instance?
@Brian_Grimal Welcome to the forums!
If the project has both ObjC as well as Swift code, the Realm file itself doesn’t care and is platform agnostic so why try to access Realm using legacy ObjC calls? How about accessing it using Swift?
More of a thought experiment than a code question. So yes, writing a Swift wrapper for the legacy ObjC classes is easy enough, but I did find you can do something like this too. Probably not the smartest thing, but it would probably work.
@objc func rlmRealm() -> RLMRealm? {
guard let realm = self.realm else { return nil }
return ObjectiveCSupport.convert(object: realm)
}
Swift vs. ObjC properties need a little attention too (e.g. List vs NSArray, Double vs NSNumber). Still figuring out best practices here.
Let me attempt to add some clarity but it’s been a while since I’ve used ObjC so anyone can feel free to correct me.
If you write a Realm app in pure ObjC, and then write a Realm App in Swift, both apps can access the same underlying Realm Objects and data. Just like your Android app can access the same data using Android Objects.
If your project is already both, you may not need to write a wrapper for ObjC classes - they will work side by side with Swift Classes as the underlying data is the same.
Hmm. Those data types do not tie to Realm objects in that way. For example, there is no List vs NSArray. NSArray is purely an ObjC construct and unrelated to Realm. An NSArray more corresponds to Swift Array. In Realm, we’ve always had a List
object.
A RealmSwift List
is tied to an Realm ObjC List
, and a Double
is well… a Double
, with the note that a optional Double is a special case.
The datatypes can be be compared here Supported Types and there’s a tab so you can look between the Swift and ObjC Pre 10.10 types.
I other words in Swift a Double
property is managed like this:
@Persisted var doubleName: Double
in ObjC (pre 10.0) it’s this
@objc dynamic var doubleName: Double = 0.0
likewise, RealmSwift List is this:
@Persisted var listName: RealmSwift.List<SomeRealmObjectTypeOrPrimative>
and in Realm ObjC:
let someList = List<Type>()
Feel free to chime in if I stated anything incorrectly.
Thanks for the link to the supported data types, that helps.
If I export the model as a Swift class, there’s by default nothing exposed to ObjC. You can annotate the class and some of the properties with @objc, except for certain optionals and lists that can’t be represented. For those it looks like you would have to create your own setters and getters. Is that correct? Something perhaps like this, or is there a better way?
@objc class SomeObject: Object {
@Persisted(primaryKey: true) var _id: ObjectId?
@Persisted @objc var aBoolean: Bool
@Persisted var anOptionalBoolean: Bool?
@Persisted @objc var aDouble: Double
@Persisted var anOptionalDouble: Double?
@Persisted @objc aString: String?
@Persisted var aListOfStrings: List<String>
}
@objc extension SomeObject {
@objc var objC_anOptionalBoolean: ObjCBool {
get { return ObjCBool(self.anOptionalBoolean ?? false) }
set { self.anOptionalBoolean = newValue.boolValue }
}
@objc var objC_anOptionalDouble: NSNumber {
get { return NSNumber(floatLiteral: self.anOptionalDouble ?? 0.0) }
set { self.anOptionalDouble = newValue.doubleValue }
}
@objc var objC_aListOfStrings: NSArray {
get {
let ret = NSMutableArray()
for idx in 0..<self.aListOfStrings.count { ret[idx] = self.details[idx] }
return ret as NSArray
}
set {
self.aListOfStrings = List<String>()
for item in newValue { self.aListOfStrings.append(item as! String) }
}
}
}
In objC, you can then do:
SomeObject *yourObj = [SomeObject new];
yourObj.aBoolean = true;
bool aBoolean = yourObj.aBoolean;
yourObj.objC_anOptionalBoolean = true;
bool anOptionalBoolean = yourObj.objC_anOptionalBoolean;
yourObj.aDouble = 1.23;
double aDouble = yourObj.aDouble;
yourObj.objC_anOptionalDouble = 1.23;
double anOptionalDouble = yourObj.objC_anOptionalDouble;
NSString *aString = yourObj.aString;
yourObj.objC_aListOfStrings = @[@"Hello", @"World"];
NSArray<NSString *> *aListOfStrings = yourObj.objC_aListOfStrings;
I am not clear on the use case of that code, but my gut feeling is it’s more complicated that it needs to be.
Given a SomeObject model which contains a mix of ObjC and Swift
@objc class SomeObject: Object {
@Persisted(primaryKey: true) var _id: ObjectId?
@Persisted @objc var aBoolean: Bool
@Persisted var anOptionalBoolean: Bool?
@Persisted @objc var aDouble: Double
@Persisted var anOptionalDouble: Double?
@Persisted var aListOfStrings: List<String>
}
Here’s how it’s populated and written
let x = SomeObject()
x.aBoolean = true
x.anOptionalBoolean = true
x.aDouble = 3.14
x.anOptionalDouble = 3.14
x.aListOfStrings.append(objectsIn: ["a", "b", "c"])
try! realm.write {
realm.add(x)
}
and then to read and work with that object
let myObject = realm.objects(SomeObject.self).first! //assuming it was written successfully
print(myObject.aBoolean)
print(myObject.anOptionalBoolean)
print(myObject.aDouble)
print(myObject.anOptionalDouble)
print(myObject.aListOfStrings)
and the output
true
Optional(true)
3.14
Optional(3.14)
List<string> <0x600002f0de40> (
[0] a,
[1] b,
[2] c
)
As you can see, you don’t need a wrapper, an extension, or really anything else to work with the properties of the object.
Note I removed @Persisted @objc aString: String?
as it’s not valid without a ‘var’
So now the important bit. If the @ObjC object is completely commented out in code, it could simply be replaced with a pure Swift version
class SomeObject: Object {
@Persisted(primaryKey: true) var _id: ObjectId?
@Persisted var aBoolean: Bool
@Persisted var anOptionalBoolean: Bool?
@Persisted var aDouble: Double
@Persisted var anOptionalDouble: Double?
@Persisted var aListOfStrings: List<String>
}
And everything works as expected.
Another thing to note is that Realm was/is actually ObjC under the hood - the ObjectiveCSupport class provides the interoperability so as projects are moved from ObjC to Swift, in many cases it’s transparent. e.g. Results in Swift is RLMResults in ObjC, a Swift List is actually a RLMArray in ObjC.
However, you’ll never need to know that. Just write Swift code and Realm will take care of the underlying assignments.
Everything you’re saying about Swift is all fine and good. You haven’t posted any Objective C code though, which is where my questions lie.
What I’m looking for is the best practices for integrating Realm into a project where a substantial portion of the code is still Objective C. New work being in Swift. The goal being a stable and supportable integration to maintain that book of work still in ObjC, providing the data via Realm.
Maybe the simple solution is to step back and use an Objective C Realm model class instead of Swift, and expose them to Swift via the bridging header.
No ObjC code was provided as it’s not clear (to me) what it’s needed for since everything new is Swift and everything legacy is ObjC.
The old code and models will continue to work as-is even after adding new Swift code and new models.
So, you can create your new Swift models and interact with them in a Swift way. If desired, the existing legacy ObjC models could be replaced over time with Swift models and refactor the code to access them in a Swift way (maybe not necessary).
Remember - the underlying data is the same so making an identical model in Swift can access that same data per my above example.
We’re kinda talking at a 10,000’ level here - IMO, there really isn’t a best practice since ObjC can live within the same project as Swift and the underlying data is the same and can be accessed by either.
Using your above example:
@objc var objC_anOptionalBoolean: ObjCBool {
get { return ObjCBool(self.anOptionalBoolean ?? false) }
set { self.anOptionalBoolean = newValue.boolValue }
}
What is the purpose of that code? You can have an optional boolean in both Swift
@Persisted var optBoolName: Bool?
and ObjC
let value = RealmProperty<Bool?>()
so why add an extension since that functionality already exists?
I think it would provide clarity and help us (me, lol) understand what the use case is if you can provide a specific example of what kind of ObjC code you need for a specific task. Just need to clarify on what that is.