From 22acedc08f12e29e925bd97ef7f9342cd2b44d35 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Mon, 12 Feb 2024 23:56:48 +0100 Subject: [PATCH 1/4] Add an empty changelog section --- CHANGELOG.md | 59 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 466a2c8dbb..bef68a016c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +x.y.z Release notes (yyyy-MM-dd) +============================================================= +### Enhancements +* None. + +### Fixed +* ([#????](https://github.com/realm/realm-swift/issues/????), since v?.?.?) +* None. + + + +### Compatibility +* Realm Studio: 14.0.1 or later. +* APIs are backwards compatible with all previous releases in the 10.x.y series. +* Carthage release for Swift is built with Xcode 15.2.0. +* CocoaPods: 1.10 or later. +* Xcode: 14.2-15.2.0. + +### Internal +* Upgraded realm-core from ? to ? + 10.47.0 Release notes (2024-02-12) ============================================================= @@ -956,7 +977,7 @@ supported version. ### Deprecations -* `App.SyncManager.logLevel` and `App.SyncManager.logFunction` are deprecated in favour of +* `App.SyncManager.logLevel` and `App.SyncManager.logFunction` are deprecated in favour of setting a default logger. ### Compatibility @@ -1688,7 +1709,7 @@ The prebuilt binary for Carthage is now build with Xcode 14.0.1. object ids. ### Breaking Changes * Private API `_realmColumnNames` has been renamed to a new public API - called `propertiesMapping()`. This change only affects the Swift API + called `propertiesMapping()`. This change only affects the Swift API and doesn't have any effects in the obj-c API. ### Compatibility @@ -1793,7 +1814,7 @@ The prebuilt binary for Carthage is now build with Xcode 14.0.1. sectionedResults = realm.objects(DemoObject.self) .sectioned(by: \.firstLetter, ascending: true) ``` -* Add `@ObservedSectionedResults` for SwiftUI support. This property wrapper type retrieves sectioned results +* Add `@ObservedSectionedResults` for SwiftUI support. This property wrapper type retrieves sectioned results from a Realm using a keyPath or callback to determine the section key. ```swift struct DemoView: View { @@ -2325,7 +2346,7 @@ Non-synchronized Realm files remain backwards-compatible. then the client reset process reverts to manual mode. - The realm's underlying object accessors remain bound so the UI may be updated in a non-disruptive way. * Added support for client reset notification blocks for `.discardLocal` and `RLMClientResetDiscardLocal` - - **RealmSwift implementation**: `discardLocal(((Realm) -> Void)? = nil, ((Realm, Realm) -> Void)? = nil)` + - **RealmSwift implementation**: `discardLocal(((Realm) -> Void)? = nil, ((Realm, Realm) -> Void)? = nil)` - RealmSwift client reset blocks are set when initializing the user configuration ```swift var configuration = user.configuration(partitionValue: "myPartition", clientResetMode: .discardLocal(beforeClientResetBlock, afterClientResetBlock)) @@ -2380,7 +2401,7 @@ Non-synchronized Realm files remain backwards-compatible. `.environment(\.realmConfiguration, ...)` if they did not match ([Cocoa #7463](https://github.com/realm/realm-swift/issues/7463), since v10.6.0). * Fix searchable component filter overriding the initial filter on `@ObservedResults`, (since v10.23.0). -* Comparing `Results`, `LinkingObjects` or `AnyRealmCollection` when using Realm via XCFramework +* Comparing `Results`, `LinkingObjects` or `AnyRealmCollection` when using Realm via XCFramework would result in compile time errors ([Cocoa #7615](https://github.com/realm/realm-swift/issues/7615), since v10.21.0) * Opening an encrypted Realm while the keychain is locked on macOS would crash ([#7438](https://github.com/realm/realm-swift/issues/7438)). @@ -2443,7 +2464,7 @@ no functional changes from 10.24.0. ### Enhancements -* Add ability to use Swift Query syntax in `@ObservedResults`, which allows you +* Add ability to use Swift Query syntax in `@ObservedResults`, which allows you to filter results using the `where` parameter. ### Fixed @@ -2507,7 +2528,7 @@ no functional changes from 10.24.0. be copied to the `fileURL` of the new Realm. If a Realm file already exists at the desitnation path, the seed file will not be copied and the already existing Realm will be opened instead. Note that to use this parameter with a synced Realm configuration - the seed Realm must be appropriately copied to a destination with + the seed Realm must be appropriately copied to a destination with `Realm.writeCopy(configuration:)`/`[RLMRealm writeCopyForConfiguration:]` first. * Add ability to permanently delete a User from a MongoDB Realm app. This can be invoked with `User.delete()`/`[RLMUser deleteWithCompletion:]`. @@ -2610,7 +2631,7 @@ no functional changes from 10.24.0. ### Enhancements * Add `metadata` property to `RLMUserProfile`/`UserProfile`. -* Add class `Projection` to allow creation of light weight view models out of Realm Objects. +* Add class `Projection` to allow creation of light weight view models out of Realm Objects. ```swift public class Person: Object { @Persisted var firstName = "" @@ -3046,15 +3067,15 @@ r `User.linkUser` methods. ### Enhancements -* Sync logs now contain information about what object/changeset was being applied when the exception was thrown. +* Sync logs now contain information about what object/changeset was being applied when the exception was thrown. ([Core #4836](https://github.com/realm/realm-core/issues/4836)) -* Added ServiceErrorCode for wrong username/password when using '`App.login`. +* Added ServiceErrorCode for wrong username/password when using '`App.login`. ([Core #7380](https://github.com/realm/realm-swift/issues/7380) ### Fixed * Fix crash in `MongoCollection.findOneDocument(filter:)` that occurred when no results were - found for a given filter. + found for a given filter. ([Cocoa #7380](https://github.com/realm/realm-swift/issues/7380), since v10.0.0) * Some of the SwiftUI property wrappers incorrectly required objects to conform to ObjectKeyIdentifiable rather than Identifiable. @@ -3758,7 +3779,7 @@ Xcode 12.2 is now the minimum supported version. ### Enhancements -* Add `@StateRealmObject` for SwiftUI support. This property wrapper type instantiates an observable object on a View. +* Add `@StateRealmObject` for SwiftUI support. This property wrapper type instantiates an observable object on a View. Use in place of `SwiftUI.StateObject` for Realm `Object`, `List`, and `EmbeddedObject` types. * Add `@ObservedRealmObject` for SwiftUI support. This property wrapper type subscribes to an observable object and invalidates a view whenever the observable object changes. Use in place of `SwiftUI.ObservedObject` for @@ -3766,12 +3787,12 @@ Xcode 12.2 is now the minimum supported version. * Add `@ObservedResults` for SwiftUI support. This property wrapper type retrieves results from a Realm. The results use the realm configuration provided by the environment value `EnvironmentValues.realmConfiguration`. * Add `EnvironmentValues.realm` and `EnvironmentValues.realmConfiguration` for `Realm` - and `Realm.Configuration` types respectively. Values can be injected into views using the `View.environment` method, e.g., `MyView().environment(\.realmConfiguration, Realm.Configuration(fileURL: URL(fileURLWithPath: "myRealmPath.realm")))`. + and `Realm.Configuration` types respectively. Values can be injected into views using the `View.environment` method, e.g., `MyView().environment(\.realmConfiguration, Realm.Configuration(fileURL: URL(fileURLWithPath: "myRealmPath.realm")))`. The value can then be declared on the example `MyView` as `@Environment(\.realm) var realm`. -* Add `SwiftUI.Binding` extensions where `Value` is of type `Object`, `List`, or `EmbeddedObject`. - These extensions expose methods for wrapped write transactions, to avoid boilerplate within +* Add `SwiftUI.Binding` extensions where `Value` is of type `Object`, `List`, or `EmbeddedObject`. + These extensions expose methods for wrapped write transactions, to avoid boilerplate within views, e.g., `TextField("name", $personObject.name)` or `$personList.append(Person())`. -* Add `Object.bind` and `EmbeddedObject.bind` for SwiftUI support. This allows you to create +* Add `Object.bind` and `EmbeddedObject.bind` for SwiftUI support. This allows you to create bindings of realm properties when a propertyWrapper is not available for you to do so, e.g., `TextField("name", personObject.bind(\.name))`. * The Sync client now logs error messages received from server rather than just the size of the error message. @@ -4603,7 +4624,7 @@ later will be able to open the new file format. owned by a single parent object, and are deleted when that parent object is deleted. They are defined by subclassing `EmbeddedObject` / `RLMEmbeddedObject` rather than `Object` / `RLMObject`. -* Add `-[RLMSyncUser customData]`/`SyncUser.customData`. Custom data is +* Add `-[RLMSyncUser customData]`/`SyncUser.customData`. Custom data is configured in your MongoDB Realm App. * Add `-[RLMApp callFunctionNamed:arguments]`/`RealmApp.functions`. This is the entry point for calling Remote MongoDB Realm functions. Functions allow you @@ -5257,7 +5278,7 @@ later will be able to open the new file format. owned by a single parent object, and are deleted when that parent object is deleted. They are defined by subclassing `EmbeddedObject` / `RLMEmbeddedObject` rather than `Object` / `RLMObject`. -* Add `-[RLMSyncUser customData]`/`SyncUser.customData`. Custom data is +* Add `-[RLMSyncUser customData]`/`SyncUser.customData`. Custom data is configured in your MongoDB Realm App. * Add `-[RLMApp callFunctionNamed:arguments]`/`RealmApp.functions`. This is the entry point for calling Remote MongoDB Realm functions. Functions allow you @@ -5456,7 +5477,7 @@ later will be able to open the new file format. NOTE: This version bumps the Realm file format to version 10. It is not possible to downgrade version 9 or earlier. Files created with older versions -of Realm will be automatically upgraded. Only +of Realm will be automatically upgraded. Only [Studio 3.11](https://github.com/realm/realm-studio/releases/tag/v3.11.0) or later will be able to open the new file format. From e507c7684114817982377ff5c830b0b70f108e9a Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Tue, 13 Feb 2024 00:10:19 +0100 Subject: [PATCH 2/4] Lift restriction on objects with computed properties --- CHANGELOG.md | 2 +- Realm/RLMObjectSchema.mm | 5 +-- Realm/Tests/Swift/SwiftSchemaTests.swift | 25 +++++++++++++- RealmSwift/Tests/SwiftLinkTests.swift | 42 ++++++++++++++++++++++++ RealmSwift/Tests/SwiftTestObjects.swift | 9 +++++ 5 files changed, 79 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bef68a016c..5ac3c85eae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ x.y.z Release notes (yyyy-MM-dd) ============================================================= ### Enhancements -* None. +* Lifted a limitation that would prevent declaring a model with only computed properties. ([Issue #8414](https://github.com/realm/realm-swift/issues/8414)) ### Fixed * ([#????](https://github.com/realm/realm-swift/issues/????), since v?.?.?) diff --git a/Realm/RLMObjectSchema.mm b/Realm/RLMObjectSchema.mm index 774d9c2b33..c935fa7b06 100644 --- a/Realm/RLMObjectSchema.mm +++ b/Realm/RLMObjectSchema.mm @@ -211,8 +211,9 @@ + (instancetype)schemaForObjectClass:(Class)objectClass { if ([objectClass shouldIncludeInDefaultSchema] && schema.isSwiftClass - && schema.properties.count == 0) { - @throw RLMException(@"No properties are defined for '%@'. Did you remember to mark them with '@objc' in your model?", schema.className); + && schema.properties.count == 0 + && schema.computedProperties.count == 0) { + @throw RLMException(@"No properties are defined for '%@'. Did you remember to mark them with '@objc' or '@Persisted' in your model?", schema.className); } return schema; } diff --git a/Realm/Tests/Swift/SwiftSchemaTests.swift b/Realm/Tests/Swift/SwiftSchemaTests.swift index 2099f0d38b..6c96571493 100644 --- a/Realm/Tests/Swift/SwiftSchemaTests.swift +++ b/Realm/Tests/Swift/SwiftSchemaTests.swift @@ -132,6 +132,19 @@ class NoProps: FakeObject { // no @objc properties } +class OnlyComputedSource: RLMObject { + @objc dynamic var link: OnlyComputedProps? +} + +class OnlyComputedProps: RLMObject { + @objc dynamic var id = 0 + @objc dynamic var backlinks: RLMLinkingObjects? + + override class func linkingObjectsProperties() -> [String : RLMPropertyDescriptor] { + return ["backlinks": RLMPropertyDescriptor(with: OnlyComputedSource.self, propertyName: "link")] + } +} + @MainActor class RequiresObjcName: RLMObject { static var enable = false @@ -170,7 +183,17 @@ class SwiftRLMSchemaTests: RLMMultiProcessTestCase { func testShouldRaiseObjectWithoutProperties() { assertThrowsWithReasonMatching(RLMObjectSchema(forObjectClass: NoProps.self), - "No properties are defined for 'NoProps'. Did you remember to mark them with '@objc' in your model?") + "No properties are defined for 'NoProps'. Did you remember to mark them with '@objc' or '@Persisted' in your model?") + } + + func testShouldNotThrowForObjectWithOnlyComputedProps() { + let config = RLMRealmConfiguration.default() + config.objectClasses = [OnlyComputedProps.self, OnlyComputedSource.self] + config.inMemoryIdentifier = #function + let r = try! RLMRealm(configuration: config) + try! r.transaction { + _ = OnlyComputedProps.create(in: r, withValue: []) + } } func testSchemaInitWithLinkedToObjectUsingInitWithValue() { diff --git a/RealmSwift/Tests/SwiftLinkTests.swift b/RealmSwift/Tests/SwiftLinkTests.swift index 25366c44e5..0073d915f7 100644 --- a/RealmSwift/Tests/SwiftLinkTests.swift +++ b/RealmSwift/Tests/SwiftLinkTests.swift @@ -111,4 +111,46 @@ class SwiftLinkTests: TestCase { XCTAssertEqual(0, owners.count) } + + func testLinkingObjectsWithNoPersistedProps() { + let realm = realmWithTestPath() + + let target = OnlyComputedProps() + + let source1 = LinkToOnlyComputed() + source1.value = 1 + source1.link = target + + XCTAssertEqual(0, target.backlinks.count, "Linking objects are not available until the object is persisted") + + try! realm.write { + realm.add(source1) + } + + XCTAssertEqual(1, target.backlinks.count) + XCTAssertEqual(source1.value, target.backlinks.first!.value) + + let source2 = LinkToOnlyComputed() + source2.value = 2 + source2.link = target + + XCTAssertEqual(1, target.backlinks.count, "Linking objects to an unpersisted object are not available") + try! realm.write { + realm.add(source2) + } + + XCTAssertEqual(2, target.backlinks.count) + XCTAssertTrue(target.backlinks.contains(where: { o in + o.value == 2 + })) + + let targetWithNoLinks = OnlyComputedProps() + try! realm.write { + // Implicitly verify we can persist a RealmObject with no persisted properties and + // no objects linking to it + realm.add(targetWithNoLinks) + } + + XCTAssertEqual(0, targetWithNoLinks.backlinks.count, "Linking objects are not available until the object is persisted") + } } diff --git a/RealmSwift/Tests/SwiftTestObjects.swift b/RealmSwift/Tests/SwiftTestObjects.swift index 27cad91675..2613b9c01c 100644 --- a/RealmSwift/Tests/SwiftTestObjects.swift +++ b/RealmSwift/Tests/SwiftTestObjects.swift @@ -868,3 +868,12 @@ class ObjectWithNestedEmbeddedObject: Object { private class PrivateObjectSubclass: Object { @objc dynamic var value = 0 } + +class LinkToOnlyComputed: Object { + @Persisted var value: Int = 0 + @Persisted var link: OnlyComputedProps? +} + +class OnlyComputedProps: Object { + @Persisted(originProperty: "link") var backlinks: LinkingObjects +} From 1c7a2bc2ec4da30fead2083f897d24adf0d0b88e Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Wed, 21 Feb 2024 00:50:55 +0100 Subject: [PATCH 3/4] Fix lint violations, rename class --- Realm/Tests/Swift/SwiftSchemaTests.swift | 9 ++++----- RealmSwift/Tests/SwiftLinkTests.swift | 18 +++++++++--------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/Realm/Tests/Swift/SwiftSchemaTests.swift b/Realm/Tests/Swift/SwiftSchemaTests.swift index 6c96571493..aaa8520002 100644 --- a/Realm/Tests/Swift/SwiftSchemaTests.swift +++ b/Realm/Tests/Swift/SwiftSchemaTests.swift @@ -133,11 +133,10 @@ class NoProps: FakeObject { } class OnlyComputedSource: RLMObject { - @objc dynamic var link: OnlyComputedProps? + @objc dynamic var link: OnlyComputedTarget? } -class OnlyComputedProps: RLMObject { - @objc dynamic var id = 0 +class OnlyComputedTarget: RLMObject { @objc dynamic var backlinks: RLMLinkingObjects? override class func linkingObjectsProperties() -> [String : RLMPropertyDescriptor] { @@ -188,11 +187,11 @@ class SwiftRLMSchemaTests: RLMMultiProcessTestCase { func testShouldNotThrowForObjectWithOnlyComputedProps() { let config = RLMRealmConfiguration.default() - config.objectClasses = [OnlyComputedProps.self, OnlyComputedSource.self] + config.objectClasses = [OnlyComputedTarget.self, OnlyComputedSource.self] config.inMemoryIdentifier = #function let r = try! RLMRealm(configuration: config) try! r.transaction { - _ = OnlyComputedProps.create(in: r, withValue: []) + _ = OnlyComputedTarget.create(in: r, withValue: []) } } diff --git a/RealmSwift/Tests/SwiftLinkTests.swift b/RealmSwift/Tests/SwiftLinkTests.swift index 0073d915f7..91d6718e55 100644 --- a/RealmSwift/Tests/SwiftLinkTests.swift +++ b/RealmSwift/Tests/SwiftLinkTests.swift @@ -111,29 +111,29 @@ class SwiftLinkTests: TestCase { XCTAssertEqual(0, owners.count) } - + func testLinkingObjectsWithNoPersistedProps() { let realm = realmWithTestPath() - + let target = OnlyComputedProps() - + let source1 = LinkToOnlyComputed() source1.value = 1 source1.link = target - + XCTAssertEqual(0, target.backlinks.count, "Linking objects are not available until the object is persisted") - + try! realm.write { realm.add(source1) } - + XCTAssertEqual(1, target.backlinks.count) XCTAssertEqual(source1.value, target.backlinks.first!.value) - + let source2 = LinkToOnlyComputed() source2.value = 2 source2.link = target - + XCTAssertEqual(1, target.backlinks.count, "Linking objects to an unpersisted object are not available") try! realm.write { realm.add(source2) @@ -143,7 +143,7 @@ class SwiftLinkTests: TestCase { XCTAssertTrue(target.backlinks.contains(where: { o in o.value == 2 })) - + let targetWithNoLinks = OnlyComputedProps() try! realm.write { // Implicitly verify we can persist a RealmObject with no persisted properties and From a7d180c4159b669e47cdb47e45de02284147dd7b Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Mon, 4 Mar 2024 15:44:03 +0100 Subject: [PATCH 4/4] Address CR comments --- CHANGELOG.md | 2 +- Realm/Tests/Swift/SwiftSchemaTests.swift | 17 ++++++++++++++++- RealmSwift/Tests/SwiftLinkTests.swift | 16 +++++++--------- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ac3c85eae..8ca77be2b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ x.y.z Release notes (yyyy-MM-dd) ============================================================= ### Enhancements -* Lifted a limitation that would prevent declaring a model with only computed properties. ([Issue #8414](https://github.com/realm/realm-swift/issues/8414)) +* Lifted a limitation that would prevent declaring a model with only computed properties. ([#8414](https://github.com/realm/realm-swift/issues/8414)) ### Fixed * ([#????](https://github.com/realm/realm-swift/issues/????), since v?.?.?) diff --git a/Realm/Tests/Swift/SwiftSchemaTests.swift b/Realm/Tests/Swift/SwiftSchemaTests.swift index aaa8520002..9836dcec0c 100644 --- a/Realm/Tests/Swift/SwiftSchemaTests.swift +++ b/Realm/Tests/Swift/SwiftSchemaTests.swift @@ -144,6 +144,12 @@ class OnlyComputedTarget: RLMObject { } } +class OnlyComputedNoBacklinksProps: FakeObject { + var computedProperty: String { + return "Test_String" + } +} + @MainActor class RequiresObjcName: RLMObject { static var enable = false @@ -185,7 +191,7 @@ class SwiftRLMSchemaTests: RLMMultiProcessTestCase { "No properties are defined for 'NoProps'. Did you remember to mark them with '@objc' or '@Persisted' in your model?") } - func testShouldNotThrowForObjectWithOnlyComputedProps() { + func testShouldNotThrowForObjectWithOnlyBacklinksProps() { let config = RLMRealmConfiguration.default() config.objectClasses = [OnlyComputedTarget.self, OnlyComputedSource.self] config.inMemoryIdentifier = #function @@ -193,6 +199,15 @@ class SwiftRLMSchemaTests: RLMMultiProcessTestCase { try! r.transaction { _ = OnlyComputedTarget.create(in: r, withValue: []) } + + let schema = OnlyComputedTarget().objectSchema + XCTAssertEqual(schema.computedProperties.count, 1) + XCTAssertEqual(schema.properties.count, 0) + } + + func testShouldThrowForObjectWithOnlyComputedNoBacklinksProps() { + assertThrowsWithReasonMatching(RLMObjectSchema(forObjectClass: OnlyComputedNoBacklinksProps.self), + "No properties are defined for 'OnlyComputedNoBacklinksProps'. Did you remember to mark them with '@objc' or '@Persisted' in your model?") } func testSchemaInitWithLinkedToObjectUsingInitWithValue() { diff --git a/RealmSwift/Tests/SwiftLinkTests.swift b/RealmSwift/Tests/SwiftLinkTests.swift index 91d6718e55..11322e2d73 100644 --- a/RealmSwift/Tests/SwiftLinkTests.swift +++ b/RealmSwift/Tests/SwiftLinkTests.swift @@ -121,28 +121,26 @@ class SwiftLinkTests: TestCase { source1.value = 1 source1.link = target - XCTAssertEqual(0, target.backlinks.count, "Linking objects are not available until the object is persisted") + XCTAssertEqual(target.backlinks.count, 0, "Linking objects are not available until the object is persisted") try! realm.write { realm.add(source1) } - XCTAssertEqual(1, target.backlinks.count) - XCTAssertEqual(source1.value, target.backlinks.first!.value) + XCTAssertEqual(target.backlinks.count, 1) + XCTAssertEqual(target.backlinks.first!.value, source1.value) let source2 = LinkToOnlyComputed() source2.value = 2 source2.link = target - XCTAssertEqual(1, target.backlinks.count, "Linking objects to an unpersisted object are not available") + XCTAssertEqual(target.backlinks.count, 1, "Linking objects to an unpersisted object are not available") try! realm.write { realm.add(source2) } - XCTAssertEqual(2, target.backlinks.count) - XCTAssertTrue(target.backlinks.contains(where: { o in - o.value == 2 - })) + XCTAssertEqual(target.backlinks.count, 2) + XCTAssertTrue(target.backlinks.contains(where: { $0.value == 2 })) let targetWithNoLinks = OnlyComputedProps() try! realm.write { @@ -151,6 +149,6 @@ class SwiftLinkTests: TestCase { realm.add(targetWithNoLinks) } - XCTAssertEqual(0, targetWithNoLinks.backlinks.count, "Linking objects are not available until the object is persisted") + XCTAssertEqual(targetWithNoLinks.backlinks.count, 0, "No object is linking to targetWithNoLinks") } }