Skip to content

Commit

Permalink
Introduce _customRealmProperties() to all Realm Objects to allow fo…
Browse files Browse the repository at this point in the history
…r 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)`
  • Loading branch information
rjchatfield committed Mar 7, 2024
1 parent 0fc34f1 commit cc6a5bc
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 2 deletions.
4 changes: 4 additions & 0 deletions Realm.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@
3FEE4F45281C439D009194C7 /* EventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEE4F44281C439D009194C7 /* EventTests.swift */; };
3FF3FFAF1F0D6D6400B84599 /* KVOTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D660FFF1BE98D880021E04F /* KVOTests.swift */; };
3FFB5AF6266ECFC7008EF2E9 /* SwiftBSONTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4996EA9F2465C44E003A1F51 /* SwiftBSONTests.swift */; };
3FFC68702B8EDB69002AE840 /* ObjectCustomPropertiesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFC686F2B8EDB69002AE840 /* ObjectCustomPropertiesTests.swift */; };
494566A9246E8C59000FD07F /* ObjectiveCSupport+BSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 494566A8246E8C59000FD07F /* ObjectiveCSupport+BSON.swift */; };
4993220A24129DCE00A0EC8E /* RLMCredentials.h in Headers */ = {isa = PBXBuildFile; fileRef = 4993220324129DCD00A0EC8E /* RLMCredentials.h */; settings = {ATTRIBUTES = (Public, ); }; };
4993220C24129DCE00A0EC8E /* RLMCredentials.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4993220424129DCD00A0EC8E /* RLMCredentials.mm */; };
Expand Down Expand Up @@ -786,6 +787,7 @@
3FEE4F3E281C4370009194C7 /* RLMEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMEvent.h; sourceTree = "<group>"; };
3FEE4F3F281C4370009194C7 /* RLMEvent.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLMEvent.mm; sourceTree = "<group>"; };
3FEE4F44281C439D009194C7 /* EventTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EventTests.swift; path = Realm/ObjectServerTests/EventTests.swift; sourceTree = "<group>"; };
3FFC686F2B8EDB69002AE840 /* ObjectCustomPropertiesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectCustomPropertiesTests.swift; sourceTree = "<group>"; };
494566A8246E8C59000FD07F /* ObjectiveCSupport+BSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ObjectiveCSupport+BSON.swift"; sourceTree = "<group>"; };
4993220224129DCD00A0EC8E /* RLMCredentials_Private.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMCredentials_Private.hpp; sourceTree = "<group>"; };
4993220324129DCD00A0EC8E /* RLMCredentials.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMCredentials.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1413,6 +1415,7 @@
CF986DE125AE3EC70039D287 /* MutableSetTests.swift */,
5D6610021BE98D880021E04F /* ObjectAccessorTests.swift */,
5D6610031BE98D880021E04F /* ObjectCreationTests.swift */,
3FFC686F2B8EDB69002AE840 /* ObjectCustomPropertiesTests.swift */,
CFFECBB225078D100010F585 /* ObjectIdTests.swift */,
5BC537151DD5B8D70055C524 /* ObjectiveCSupportTests.swift */,
5D6610041BE98D880021E04F /* ObjectSchemaInitializationTests.swift */,
Expand Down Expand Up @@ -2630,6 +2633,7 @@
3F558C8E22C29A03002F0F30 /* RLMTestObjects.m in Sources */,
3F8825091E5E335000586B35 /* SchemaTests.swift in Sources */,
CFDBC4C0288040E700EE80E6 /* SectionedResultsTests.swift in Sources */,
3FFC68702B8EDB69002AE840 /* ObjectCustomPropertiesTests.swift in Sources */,
3F88250A1E5E335000586B35 /* SortDescriptorTests.swift in Sources */,
3FFB5AF6266ECFC7008EF2E9 /* SwiftBSONTests.swift in Sources */,
3F88250B1E5E335000586B35 /* SwiftLinkTests.swift in Sources */,
Expand Down
38 changes: 36 additions & 2 deletions RealmSwift/Impl/SchemaDiscovery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ public protocol _RealmSchemaDiscoverable {
static func _rlmPopulateProperty(_ prop: RLMProperty)
}

extension RLMObjectBase {
/// Allow client code to generate properties (ie. via Swift Macros)
@_spi(RealmSwiftPrivate)
@objc open class func _customRealmProperties() -> [RLMProperty]? {
return nil
}
}

internal protocol SchemaDiscoverable: _RealmSchemaDiscoverable {}
extension SchemaDiscoverable {
public static var _rlmOptional: Bool { false }
Expand All @@ -47,8 +55,8 @@ extension SchemaDiscoverable {
public static func _rlmPopulateProperty(_ prop: RLMProperty) { }
}

internal extension RLMProperty {
convenience init(name: String, value: _RealmSchemaDiscoverable) {
extension RLMProperty {
internal convenience init(name: String, value: _RealmSchemaDiscoverable) {
let valueType = Swift.type(of: value)
self.init()
self.name = name
Expand All @@ -60,6 +68,29 @@ internal extension RLMProperty {
self.updateAccessors()
}
}

/// Exposed for Macros.
/// Important: Keep args in same order & default value as `@Persisted` property wrapper
@_spi(RealmSwiftPrivate)
public convenience init<O: ObjectBase, V: _Persistable>(
name: String,
objectType _: O.Type,
valueType _: V.Type,
indexed: Bool = false,
primaryKey: Bool = false,
originProperty: String? = nil
) {
self.init()
self.name = name
self.type = V._rlmType
self.optional = V._rlmOptional
self.indexed = primaryKey || indexed
self.isPrimary = primaryKey
self.linkOriginPropertyName = originProperty
V._rlmPopulateProperty(self)
V._rlmSetAccessor(self)
self.swiftIvar = ivar_getOffset(class_getInstanceVariable(O.self, "_" + name)!)
}
}

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

private func getProperties(_ cls: RLMObjectBase.Type) -> [RLMProperty] {
if let props = cls._customRealmProperties() {
return props
}
// Check for any modern properties and only scan for legacy properties if
// none are found.
let object = cls.init()
Expand Down
75 changes: 75 additions & 0 deletions RealmSwift/Tests/ObjectCustomPropertiesTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2024 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////

import XCTest
@_spi(RealmSwiftPrivate) import RealmSwift

final class ObjectCustomPropertiesTests: TestCase {
override func tearDown() {
super.tearDown()
CustomPropertiesObject.injected_customRealmProperties = nil
}

func testCustomProperties() throws {
CustomPropertiesObject.injected_customRealmProperties = [CustomPropertiesObject.preMadeRLMProperty]

let customProperties = try XCTUnwrap(CustomPropertiesObject._customRealmProperties())
XCTAssertEqual(customProperties.count, 1)
XCTAssert(customProperties.first === CustomPropertiesObject.preMadeRLMProperty)

// Assert properties are custom properties
let properties = CustomPropertiesObject._getProperties()
XCTAssertEqual(properties.count, 1)
XCTAssert(properties.first === CustomPropertiesObject.preMadeRLMProperty)
}

func testNoCustomProperties() {
CustomPropertiesObject.injected_customRealmProperties = nil

let customProperties = CustomPropertiesObject._customRealmProperties()
XCTAssertNil(customProperties)

// Assert properties are generated despite `nil` custom properties
let properties = CustomPropertiesObject._getProperties()
XCTAssertEqual(properties.count, 1)
XCTAssert(properties.first !== CustomPropertiesObject.preMadeRLMProperty)
}

func testEmptyCustomProperties() throws {
CustomPropertiesObject.injected_customRealmProperties = []

let customProperties = try XCTUnwrap(CustomPropertiesObject._customRealmProperties())
XCTAssertEqual(customProperties.count, 0)

// Assert properties are custom properties (rather incorrectly)
let properties = CustomPropertiesObject._getProperties()
XCTAssertEqual(properties.count, 0)
}
}

@objc(CustomPropertiesObject)
private final class CustomPropertiesObject: Object {
@Persisted var value: String

static override func _customRealmProperties() -> [RLMProperty]? {
return injected_customRealmProperties
}

static var injected_customRealmProperties: [RLMProperty]?
static let preMadeRLMProperty = RLMProperty(name: "value", objectType: CustomPropertiesObject.self, valueType: String.self)
}

0 comments on commit cc6a5bc

Please sign in to comment.