Skip to content

Commit cc6a5bc

Browse files
authored
Introduce _customRealmProperties() to all Realm Objects to allow for client-generated RLMProperties (via Swift Macros) (#8490)
* Allow client-generated realm properties - [RLMObjectBase] Add _customRealmProperties() - [RealmSwift] Check _customRealmProperties() from within getProperties() - [RLMProperty] Expose convenience init - Add tests * annotate with `@_spi(RealmSwiftPrivate)`
1 parent 0fc34f1 commit cc6a5bc

File tree

3 files changed

+115
-2
lines changed

3 files changed

+115
-2
lines changed

Realm.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@
220220
3FEE4F45281C439D009194C7 /* EventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEE4F44281C439D009194C7 /* EventTests.swift */; };
221221
3FF3FFAF1F0D6D6400B84599 /* KVOTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D660FFF1BE98D880021E04F /* KVOTests.swift */; };
222222
3FFB5AF6266ECFC7008EF2E9 /* SwiftBSONTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4996EA9F2465C44E003A1F51 /* SwiftBSONTests.swift */; };
223+
3FFC68702B8EDB69002AE840 /* ObjectCustomPropertiesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFC686F2B8EDB69002AE840 /* ObjectCustomPropertiesTests.swift */; };
223224
494566A9246E8C59000FD07F /* ObjectiveCSupport+BSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 494566A8246E8C59000FD07F /* ObjectiveCSupport+BSON.swift */; };
224225
4993220A24129DCE00A0EC8E /* RLMCredentials.h in Headers */ = {isa = PBXBuildFile; fileRef = 4993220324129DCD00A0EC8E /* RLMCredentials.h */; settings = {ATTRIBUTES = (Public, ); }; };
225226
4993220C24129DCE00A0EC8E /* RLMCredentials.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4993220424129DCD00A0EC8E /* RLMCredentials.mm */; };
@@ -786,6 +787,7 @@
786787
3FEE4F3E281C4370009194C7 /* RLMEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMEvent.h; sourceTree = "<group>"; };
787788
3FEE4F3F281C4370009194C7 /* RLMEvent.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMEvent.mm; sourceTree = "<group>"; };
788789
3FEE4F44281C439D009194C7 /* EventTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EventTests.swift; path = Realm/ObjectServerTests/EventTests.swift; sourceTree = "<group>"; };
790+
3FFC686F2B8EDB69002AE840 /* ObjectCustomPropertiesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectCustomPropertiesTests.swift; sourceTree = "<group>"; };
789791
494566A8246E8C59000FD07F /* ObjectiveCSupport+BSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ObjectiveCSupport+BSON.swift"; sourceTree = "<group>"; };
790792
4993220224129DCD00A0EC8E /* RLMCredentials_Private.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMCredentials_Private.hpp; sourceTree = "<group>"; };
791793
4993220324129DCD00A0EC8E /* RLMCredentials.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMCredentials.h; sourceTree = "<group>"; };
@@ -1413,6 +1415,7 @@
14131415
CF986DE125AE3EC70039D287 /* MutableSetTests.swift */,
14141416
5D6610021BE98D880021E04F /* ObjectAccessorTests.swift */,
14151417
5D6610031BE98D880021E04F /* ObjectCreationTests.swift */,
1418+
3FFC686F2B8EDB69002AE840 /* ObjectCustomPropertiesTests.swift */,
14161419
CFFECBB225078D100010F585 /* ObjectIdTests.swift */,
14171420
5BC537151DD5B8D70055C524 /* ObjectiveCSupportTests.swift */,
14181421
5D6610041BE98D880021E04F /* ObjectSchemaInitializationTests.swift */,
@@ -2630,6 +2633,7 @@
26302633
3F558C8E22C29A03002F0F30 /* RLMTestObjects.m in Sources */,
26312634
3F8825091E5E335000586B35 /* SchemaTests.swift in Sources */,
26322635
CFDBC4C0288040E700EE80E6 /* SectionedResultsTests.swift in Sources */,
2636+
3FFC68702B8EDB69002AE840 /* ObjectCustomPropertiesTests.swift in Sources */,
26332637
3F88250A1E5E335000586B35 /* SortDescriptorTests.swift in Sources */,
26342638
3FFB5AF6266ECFC7008EF2E9 /* SwiftBSONTests.swift in Sources */,
26352639
3F88250B1E5E335000586B35 /* SwiftLinkTests.swift in Sources */,

RealmSwift/Impl/SchemaDiscovery.swift

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ public protocol _RealmSchemaDiscoverable {
3939
static func _rlmPopulateProperty(_ prop: RLMProperty)
4040
}
4141

42+
extension RLMObjectBase {
43+
/// Allow client code to generate properties (ie. via Swift Macros)
44+
@_spi(RealmSwiftPrivate)
45+
@objc open class func _customRealmProperties() -> [RLMProperty]? {
46+
return nil
47+
}
48+
}
49+
4250
internal protocol SchemaDiscoverable: _RealmSchemaDiscoverable {}
4351
extension SchemaDiscoverable {
4452
public static var _rlmOptional: Bool { false }
@@ -47,8 +55,8 @@ extension SchemaDiscoverable {
4755
public static func _rlmPopulateProperty(_ prop: RLMProperty) { }
4856
}
4957

50-
internal extension RLMProperty {
51-
convenience init(name: String, value: _RealmSchemaDiscoverable) {
58+
extension RLMProperty {
59+
internal convenience init(name: String, value: _RealmSchemaDiscoverable) {
5260
let valueType = Swift.type(of: value)
5361
self.init()
5462
self.name = name
@@ -60,6 +68,29 @@ internal extension RLMProperty {
6068
self.updateAccessors()
6169
}
6270
}
71+
72+
/// Exposed for Macros.
73+
/// Important: Keep args in same order & default value as `@Persisted` property wrapper
74+
@_spi(RealmSwiftPrivate)
75+
public convenience init<O: ObjectBase, V: _Persistable>(
76+
name: String,
77+
objectType _: O.Type,
78+
valueType _: V.Type,
79+
indexed: Bool = false,
80+
primaryKey: Bool = false,
81+
originProperty: String? = nil
82+
) {
83+
self.init()
84+
self.name = name
85+
self.type = V._rlmType
86+
self.optional = V._rlmOptional
87+
self.indexed = primaryKey || indexed
88+
self.isPrimary = primaryKey
89+
self.linkOriginPropertyName = originProperty
90+
V._rlmPopulateProperty(self)
91+
V._rlmSetAccessor(self)
92+
self.swiftIvar = ivar_getOffset(class_getInstanceVariable(O.self, "_" + name)!)
93+
}
6394
}
6495

6596
private func getModernProperties(_ object: ObjectBase) -> [RLMProperty] {
@@ -179,6 +210,9 @@ private func getLegacyProperties(_ object: ObjectBase, _ cls: ObjectBase.Type) -
179210
}
180211

181212
private func getProperties(_ cls: RLMObjectBase.Type) -> [RLMProperty] {
213+
if let props = cls._customRealmProperties() {
214+
return props
215+
}
182216
// Check for any modern properties and only scan for legacy properties if
183217
// none are found.
184218
let object = cls.init()
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
////////////////////////////////////////////////////////////////////////////
2+
//
3+
// Copyright 2024 Realm Inc.
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
//
17+
////////////////////////////////////////////////////////////////////////////
18+
19+
import XCTest
20+
@_spi(RealmSwiftPrivate) import RealmSwift
21+
22+
final class ObjectCustomPropertiesTests: TestCase {
23+
override func tearDown() {
24+
super.tearDown()
25+
CustomPropertiesObject.injected_customRealmProperties = nil
26+
}
27+
28+
func testCustomProperties() throws {
29+
CustomPropertiesObject.injected_customRealmProperties = [CustomPropertiesObject.preMadeRLMProperty]
30+
31+
let customProperties = try XCTUnwrap(CustomPropertiesObject._customRealmProperties())
32+
XCTAssertEqual(customProperties.count, 1)
33+
XCTAssert(customProperties.first === CustomPropertiesObject.preMadeRLMProperty)
34+
35+
// Assert properties are custom properties
36+
let properties = CustomPropertiesObject._getProperties()
37+
XCTAssertEqual(properties.count, 1)
38+
XCTAssert(properties.first === CustomPropertiesObject.preMadeRLMProperty)
39+
}
40+
41+
func testNoCustomProperties() {
42+
CustomPropertiesObject.injected_customRealmProperties = nil
43+
44+
let customProperties = CustomPropertiesObject._customRealmProperties()
45+
XCTAssertNil(customProperties)
46+
47+
// Assert properties are generated despite `nil` custom properties
48+
let properties = CustomPropertiesObject._getProperties()
49+
XCTAssertEqual(properties.count, 1)
50+
XCTAssert(properties.first !== CustomPropertiesObject.preMadeRLMProperty)
51+
}
52+
53+
func testEmptyCustomProperties() throws {
54+
CustomPropertiesObject.injected_customRealmProperties = []
55+
56+
let customProperties = try XCTUnwrap(CustomPropertiesObject._customRealmProperties())
57+
XCTAssertEqual(customProperties.count, 0)
58+
59+
// Assert properties are custom properties (rather incorrectly)
60+
let properties = CustomPropertiesObject._getProperties()
61+
XCTAssertEqual(properties.count, 0)
62+
}
63+
}
64+
65+
@objc(CustomPropertiesObject)
66+
private final class CustomPropertiesObject: Object {
67+
@Persisted var value: String
68+
69+
static override func _customRealmProperties() -> [RLMProperty]? {
70+
return injected_customRealmProperties
71+
}
72+
73+
static var injected_customRealmProperties: [RLMProperty]?
74+
static let preMadeRLMProperty = RLMProperty(name: "value", objectType: CustomPropertiesObject.self, valueType: String.self)
75+
}

0 commit comments

Comments
 (0)